| Index: issue-reporter.js |
| =================================================================== |
| new file mode 100644 |
| --- /dev/null |
| +++ b/issue-reporter.js |
| @@ -0,0 +1,442 @@ |
| +/* |
| + * This file is part of Adblock Plus <https://adblockplus.org/>, |
| + * Copyright (C) 2006-present eyeo GmbH |
| + * |
| + * Adblock Plus is free software: you can redistribute it and/or modify |
| + * it under the terms of the GNU General Public License version 3 as |
| + * published by the Free Software Foundation. |
| + * |
| + * Adblock Plus is distributed in the hope that it will be useful, |
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| + * GNU General Public License for more details. |
| + * |
| + * You should have received a copy of the GNU General Public License |
| + * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
| + */ |
| + |
| +"use strict"; |
| + |
| +window.ext = {}; |
| + |
| +let reportData = new DOMParser().parseFromString("<report></report>", "text/xml"); |
| + |
| +let pages = { |
| + "typeSelectorPage": [initTypeSelector, leaveTypeSelector], |
| + "commentPage": [initCommentPage, leaveCommentPage], |
| + "sendPage": [initSendPage, leaveSendPage] |
| +}; |
| + |
| +document.addEventListener("DOMContentLoaded", () => |
| +{ |
| + document.getElementById("cancel").addEventListener("click", () => |
| + { |
| + window.close(); |
| + }); |
| + |
| + document.getElementById("continue").addEventListener("click", () => |
| + { |
| + if (!document.getElementById("continue").disabled) |
| + pages[getCurrentPage()][1](); |
| + }); |
| + |
| + document.addEventListener("keydown", event => |
| + { |
| + let blacklistedElements = new Set(["textarea", "button", "a"]) |
| + |
| + if (event.key == "Enter" && !blacklistedElements.has(event.target.localName)) |
| + document.getElementById("continue").click(); |
| + else if (event.key == "Escape") |
| + document.getElementById("cancel").click(); |
| + }); |
| + |
| + browser.runtime.sendMessage({ |
| + type: "app.get", |
| + what: "doclink", |
| + link: "reporter_privacy" |
| + }).then(url => |
| + { |
| + document.getElementById("privacyPolicy").href = url; |
| + }); |
| + |
| + initDataCollector(); |
| +}); |
| + |
| +function getCurrentPage() |
| +{ |
| + return document.querySelector(".page:not([hidden])").id; |
| +} |
| + |
| +function setCurrentPage(pageId) |
| +{ |
| + if (!pages.hasOwnProperty(pageId)) |
| + return; |
| + |
| + let previousPage = document.querySelector(".page:not([hidden])"); |
| + if (previousPage) |
| + previousPage.hidden = true; |
| + |
| + document.getElementById(pageId).hidden = false; |
| + pages[pageId][0](); |
| +} |
| + |
| +function censorURL(url) |
| +{ |
| + return url.replace(/([?;&\/#][^?;&\/#]+?=)[^?;&\/#]+/g, "$1*"); |
| +} |
| + |
| +function encodeHTML(str) |
| +{ |
| + return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """); |
| +} |
| + |
| +function serializeReportData() |
| +{ |
| + let result = new XMLSerializer().serializeToString(reportData); |
| + |
| + // Insert line breaks before each new tag |
| + result = result.replace(/(<[^\/]([^"<>]*|"[^"]*")*>)/g, "\n$1"); |
| + result = result.replace(/^\n+/, ""); |
| + return result; |
| +} |
| + |
| +function retrieveAddonInfo() |
| +{ |
| + let element = reportData.createElement("adblock-plus"); |
| + return browser.runtime.sendMessage({ |
| + type: "app.get", |
| + what: "addonVersion" |
| + }).then(addonVersion => |
| + { |
| + element.setAttribute("version", addonVersion); |
| + return browser.runtime.sendMessage({ |
| + type: "app.get", |
| + what: "localeInfo" |
| + }); |
| + }).then(({locale}) => |
| + { |
| + element.setAttribute("locale", locale); |
| + reportData.documentElement.appendChild(element); |
| + }); |
| +} |
| + |
| +function retrieveApplicationInfo() |
| +{ |
| + let element = reportData.createElement("application"); |
| + return browser.runtime.sendMessage({ |
| + type: "app.get", |
| + what: "application" |
| + }).then(application => |
| + { |
| + element.setAttribute("name", application); |
| + return browser.runtime.sendMessage({ |
| + type: "app.get", |
| + what: "applicationVersion" |
| + }); |
| + }).then(applicationVersion => |
| + { |
| + element.setAttribute("version", applicationVersion); |
| + element.setAttribute("userAgent", navigator.userAgent); |
| + reportData.documentElement.appendChild(element); |
| + }); |
| +} |
| + |
| +function retrievePlatformInfo() |
| +{ |
| + let element = reportData.createElement("platform"); |
| + return browser.runtime.sendMessage({ |
| + type: "app.get", |
| + what: "platform" |
| + }).then(platform => |
| + { |
| + element.setAttribute("name", platform); |
| + return browser.runtime.sendMessage({ |
| + type: "app.get", |
| + what: "platformVersion" |
| + }); |
| + }).then(platformVersion => |
| + { |
| + element.setAttribute("version", platformVersion); |
| + reportData.documentElement.appendChild(element); |
| + }); |
| +} |
| + |
| +function retrieveTabURL(tabId) |
| +{ |
| + return browser.tabs.get(tabId).then(tab => |
| + { |
| + let element = reportData.createElement("window"); |
| + if (tab.url) |
| + element.setAttribute("url", censorURL(tab.url)); |
| + reportData.documentElement.appendChild(element); |
| + }); |
| +} |
| + |
| +function retrieveSubscriptions() |
| +{ |
| + return browser.runtime.sendMessage({ |
| + type: "subscriptions.get", |
| + ignoreDisabled: true, |
| + downloadable: true |
| + }).then(subscriptions => |
| + { |
| + let element = reportData.createElement("subscriptions"); |
| + for (let subscription of subscriptions) |
| + { |
| + if (!/^(http|https|ftp):/.test(subscription.url)) |
| + continue; |
| + |
| + let now = Math.round(Date.now() / 1000); |
| + let subscriptionElement = reportData.createElement("subscription"); |
| + subscriptionElement.setAttribute("id", subscription.url); |
| + if (subscription.lastDownload) |
| + subscriptionElement.setAttribute("lastDownloadAttempt", subscription.lastDownload - now); |
| + subscriptionElement.setAttribute("downloadStatus", subscription.downloadStatus); |
| + element.appendChild(subscriptionElement); |
| + } |
| + reportData.documentElement.appendChild(element); |
| + }); |
| +} |
| + |
| +function initDataCollector() |
| +{ |
| + Promise.resolve().then(() => |
| + { |
| + let tabId = parseInt(location.search.replace(/^\?/, ""), 10) || 0; |
| + let handlers = [ |
| + retrieveAddonInfo(), |
| + retrieveApplicationInfo(), |
| + retrievePlatformInfo(), |
| + retrieveTabURL(tabId), |
| + retrieveSubscriptions() |
| + ]; |
| + return Promise.all(handlers); |
| + }).then(() => |
| + { |
| + setCurrentPage("typeSelectorPage"); |
| + }).catch(e => |
| + { |
| + if (!e.name && e.message) |
| + e = e.message; |
| + alert(e); |
| + window.close(); |
| + }); |
| +} |
| + |
| +function initTypeSelector() |
| +{ |
| + document.getElementById("typeFalsePositive").focus(); |
| + |
| + |
| + for (let checkbox of document.querySelectorAll("input[name='type']")) |
| + { |
| + checkbox.addEventListener("click", () => |
| + { |
| + if (document.querySelector("input[name='type']:checked")) |
| + document.getElementById("continue").disabled = false; |
| + }); |
| + } |
| +} |
| + |
| +function leaveTypeSelector() |
| +{ |
| + let checkbox = document.querySelector("input[name='type']:checked"); |
| + reportData.documentElement.setAttribute("type", checkbox.value); |
| + setCurrentPage("commentPage"); |
| +} |
| + |
| +function initCommentPage() |
| +{ |
| + let continueButton = document.getElementById("continue"); |
| + continueButton.disabled = true; |
| + continueButton.textContent = browser.i18n.getMessage("issueReporter_sendButton_label"); |
| + |
| + let emailElement = reportData.createElement("email"); |
| + let emailField = document.getElementById("email"); |
| + let anonymousSubmissionField = document.getElementById("anonymousSubmission"); |
| + let validateEmail = () => |
| + { |
| + document.getElementById("anonymousSubmissionWarning").setAttribute("data-invisible", !anonymousSubmissionField.checked); |
| + if (anonymousSubmissionField.checked) |
| + { |
| + emailField.value = ""; |
| + emailField.disabled = true; |
| + continueButton.disabled = false; |
| + if (emailElement.parentNode) |
| + emailElement.parentNode.removeChild(emailElement); |
| + } |
| + else |
| + { |
| + emailField.disabled = false; |
| + |
| + let value = emailField.value.trim(); |
| + emailElement.textContent = value; |
| + reportData.documentElement.appendChild(emailElement); |
| + continueButton.disabled = value == "" || !emailField.validity.valid; |
| + } |
| + }; |
| + emailField.addEventListener("input", validateEmail); |
| + anonymousSubmissionField.addEventListener("click", validateEmail); |
| + |
| + let commentElement = reportData.createElement("comment"); |
| + document.getElementById("comment").addEventListener("input", event => |
| + { |
| + if (commentElement.parentNode) |
| + commentElement.parentNode.removeChild(commentElement); |
| + |
| + let value = event.target.value.trim(); |
| + commentElement.textContent = value.substr(0, 1000); |
| + if (value) |
| + reportData.documentElement.appendChild(commentElement); |
| + document.getElementById("commentLengthWarning").setAttribute("data-invisible", value.length <= 1000); |
| + }); |
| + |
| + document.getElementById("showData").addEventListener("click", event => |
| + { |
| + event.preventDefault(); |
| + |
| + // window.open() won't open data: URIs in Chrome |
| + browser.tabs.getCurrent().then(tab => |
| + { |
| + browser.tabs.create({ |
| + url: "data:text/xml;charset=utf-8," + encodeURIComponent(serializeReportData()), |
| + openerTabId: tab.id |
| + }); |
| + }) |
| + }); |
| + |
| + emailField.focus(); |
| +} |
| + |
| +function leaveCommentPage() |
| +{ |
| + setCurrentPage("sendPage"); |
| +} |
| + |
| +function initSendPage() |
| +{ |
| + document.getElementById("cancel").hidden = true; |
| + |
| + let continueButton = document.getElementById("continue"); |
| + continueButton.textContent = browser.i18n.getMessage("issueReporter_doneButton_label"); |
| + continueButton.disabled = true; |
| + |
| + let uuid = new Uint16Array(8); |
| + window.crypto.getRandomValues(uuid); |
| + uuid[3] = uuid[3] & 0x0FFF | 0x4000; // version 4 |
| + uuid[4] = uuid[4] & 0x3FFF | 0x8000; // variant 1 |
| + |
| + let uuidString = ""; |
| + for (let i = 0; i < uuid.length; i++) |
| + { |
| + let component = uuid[i].toString(16); |
| + while (component.length < 4) |
| + component = "0" + component; |
| + uuidString += component; |
| + if (i >= 1 && i<= 4) |
| + uuidString += "-"; |
| + } |
| + |
| + let params = new URLSearchParams({ |
| + version: 1, |
| + guid: uuidString, |
| + lang: reportData.getElementsByTagName("adblock-plus")[0].getAttribute("locale") |
| + }); |
| + let url = "https://reports.adblockplus.org/submitReport?" + params; |
|
Felix Dahlke
2017/10/19 16:48:04
Why not make this a pref?
Wladimir Palant
2017/10/19 17:54:25
Why make it a pref? It used to be a pref in the le
Felix Dahlke
2017/10/19 18:21:31
Two reasons:
1. Feels wrong to hard code URLs in
|
| + |
| + let reportSent = event => |
| + { |
| + let success = false; |
| + let errorMessage = browser.i18n.getMessage("filters_subscription_lastDownload_connectionError"); |
| + try |
| + { |
| + success = request.status == 200; |
| + if (request.status != 0) |
| + errorMessage = request.status + " " + request.statusText; |
| + } |
| + catch (e) |
| + { |
| + // Getting request status might throw if no connection was established |
| + } |
| + |
| + let result; |
| + try |
| + { |
| + result = request.responseText; |
| + } |
| + catch (e) |
| + { |
| + result = ""; |
| + } |
| + |
| + if (!success) |
| + { |
| + let errorElement = document.getElementById("error"); |
| + let template = browser.i18n.getMessage("issueReporter_errorMessage").replace(/[\r\n\s]+/g, " "); |
| + |
| + let [, beforeLink, linkText, afterLink] = /(.*)\[link\](.*)\[\/link\](.*)/.exec(template) || [null, "", template, ""]; |
| + beforeLink = beforeLink.replace(/\?1\?/g, errorMessage); |
| + afterLink = afterLink.replace(/\?1\?/g, errorMessage); |
| + |
| + while (errorElement.firstChild) |
| + errorElement.removeChild(errorElement.firstChild); |
| + |
| + let link = document.createElement("a"); |
| + link.textContent = linkText; |
| + browser.runtime.sendMessage({ |
| + type: "app.get", |
| + what: "doclink", |
| + link: "reporter_connect_issue" |
| + }).then(url => |
| + { |
| + link.href = url; |
| + }); |
| + |
| + |
| + errorElement.appendChild(document.createTextNode(beforeLink)); |
| + errorElement.appendChild(link); |
| + errorElement.appendChild(document.createTextNode(afterLink)); |
| + |
| + errorElement.hidden = false; |
| + } |
| + |
| + result = result.replace(/%CONFIRMATION%/g, encodeHTML(browser.i18n.getMessage("issueReporter_confirmationMessage"))); |
| + result = result.replace(/%KNOWNISSUE%/g, encodeHTML(browser.i18n.getMessage("issueReporter_knownIssueMessage"))); |
| + result = result.replace(/(<html)\b/, '$1 dir="' + encodeHTML(window.getComputedStyle(document.documentElement, "").direction + '"')); |
| + |
| + document.getElementById("sendReportMessage").hidden = true; |
| + document.getElementById("sendingProgressContainer").hidden = true; |
| + |
| + let resultFrame = document.getElementById("result"); |
| + resultFrame.setAttribute("src", "data:text/html;charset=utf-8," + encodeURIComponent(result)); |
| + resultFrame.hidden = false; |
| + |
| + document.getElementById("continue").disabled = false; |
| + }; |
| + |
| + let request = new XMLHttpRequest(); |
| + request.open("POST", url); |
| + request.setRequestHeader("Content-Type", "text/xml"); |
| + request.setRequestHeader("X-Adblock-Plus", "1"); |
| + request.addEventListener("load", reportSent); |
| + request.addEventListener("error", reportSent); |
| + request.upload.addEventListener("progress", event => |
| + { |
| + if (!event.lengthComputable) |
| + return; |
| + |
| + let progress = Math.round(event.loaded / event.total * 100); |
| + if (event.loaded > 0) |
| + { |
| + let progress = document.getElementById("sendingProgress"); |
| + progress.max = event.total; |
| + progress.value = event.loaded; |
| + } |
| + }); |
| + request.send(serializeReportData()); |
| +} |
| + |
| +function leaveSendPage() |
| +{ |
| + window.close(); |
| +} |