diff --git a/_locales/de/messages.json b/_locales/de/messages.json index 8725bba..cba7fce 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -8,6 +8,15 @@ "description": "" }, + "more": { + "message": "mehr", + "description": "" + }, + "less": { + "message": "weniger", + "description": "" + }, + "displayAdvancedSettings_title": { "message": "Expertenmodus", "description": "" diff --git a/_locales/en/messages.json b/_locales/en/messages.json index e021b01..02d74bd 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -7,6 +7,15 @@ "message": "Changes the JS-API for modifying to prevent Canvas-Fingerprinting.", "description": "" }, + + "more": { + "message": "more", + "description": "" + }, + "less": { + "message": "less", + "description": "" + }, "displayAdvancedSettings_title": { "message": "Expert mode", diff --git a/pageAction/domainNotification.js b/pageAction/domainNotification.js new file mode 100644 index 0000000..3eb9c34 --- /dev/null +++ b/pageAction/domainNotification.js @@ -0,0 +1,143 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +(function(){ + "use strict"; + + const {createCollapser, createActionButtons} = require("./gui"); + + const actions = []; + const addAction = function addAction(action){ + actions.push(action); + }; + + const addToContainer = function(){ + const container = document.getElementById("prints"); + var first = true; + + return function addToContainer(domainNotification){ + if (first){ + container.innerHTML = ""; + first = false; + } + container.appendChild(domainNotification.node()); + }; + }(); + + const DomainNotification = function DomainNotification(domain, messageId){ + this.domain = domain; + this.messageId = messageId; + addToContainer(this); + this.update(); + }; + + DomainNotification.prototype.notifications = function notifications(){ + const notifications = []; + this.notifications = function(){ + return notifications; + }; + return notifications; + }; + + DomainNotification.prototype.addNotification = function addNotification(notification){ + this.notifications().push(notification); + this.notificationsNode().appendChild(notification.node()); + this.update(); + }; + + // DOM node creation functions + + DomainNotification.prototype.node = function node(){ + const node = document.createElement("li"); + node.className = "domainPrints collapsable collapsed"; + node.appendChild(this.textNode()); + createCollapser(node); + node.appendChild(this.notificationsNode()); + node.appendChild(this.actionsNode()); + + this.node = function(){ + return node; + }; + return node; + }; + DomainNotification.prototype.update = function update(){ + this.updateTextNode(); + this.notifications().forEach(function(notification){ + notification.update(); + }); + }; + + DomainNotification.prototype.textNode = function textNode(){ + const node = document.createElement("span"); + this.textNode = function(){ + return node; + }; + var messageParts = browser.i18n.getMessage(this.messageId).split(/\{url\}/g); + node.appendChild(document.createTextNode(messageParts.shift())); + while (messageParts.length){ + var urlSpan = document.createElement("span"); + urlSpan.textContent = this.domain; + urlSpan.className = "url"; + node.appendChild(urlSpan); + node.appendChild(document.createTextNode(messageParts.shift())); + } + node.appendChild(document.createTextNode(" (")); + var countSpan = document.createElement("span"); + countSpan.className = "count"; + countSpan.innerHTML = "0"; + node.appendChild(countSpan); + node.appendChild(document.createTextNode(") ")); + + return node; + }; + DomainNotification.prototype.updateTextNode = function updateTextNode(){ + const node = this.textNode(); + const notifications = this.notifications(); + const urls = notifications.map(function(not){ + return not.url; + }).filter(function(url, i, urls){ + return urls.indexOf(url) === i; + }).join("\n"); + node.querySelectorAll(".url").forEach(function(urlSpan){ + urlSpan.title = urls; + }); + + node.title = notifications.map(function(notification){ + return notification.timestamp + ": " + notification.functionName; + }).join("\n"); + + node.querySelectorAll(".count").forEach(function(countSpan){ + countSpan.innerHTML = notifications.length; + }); + }; + + DomainNotification.prototype.actionsNode = function actionsNode(){ + const node = document.createElement("div"); + createActionButtons(node, actions, this.domain); + this.actionsNode = function(){ + return node; + }; + return node; + }; + + DomainNotification.prototype.notificationsNode = function notificationsNode(){ + const node = document.createElement("ul"); + node.className = "notifications collapsing"; + this.notificationsNode = function(){ + return node; + }; + return node; + }; + + const domains = new Map(); + const domainNotification = function(domain, messageId){ + var domainNotification = domains.get(domain + messageId); + if (!domainNotification){ + domainNotification = new DomainNotification(domain, messageId); + domains.set(domain + messageId, domainNotification); + } + return domainNotification; + }; + domainNotification.addAction = addAction; + window.scope.domainNotification = domainNotification; +}()); \ No newline at end of file diff --git a/pageAction/gui.js b/pageAction/gui.js new file mode 100644 index 0000000..ba8db62 --- /dev/null +++ b/pageAction/gui.js @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +(function(){ + "use strict"; + + const scope = window.scope.gui = {}; + + const {error, warning, message, notice, verbose, setPrefix: setLogPrefix} = require("./logging"); + + scope.createCollapser = function(){ + const more = browser.i18n.getMessage("more"); + const less = browser.i18n.getMessage("less"); + + return function createCollapser(container){ + var collapser = document.createElement("span"); + collapser.className = "collapser"; + + collapser.innerHTML = `${more}${less}`; + + container.appendChild(collapser); + collapser.addEventListener("click", function(){ + container.classList.toggle("collapsed"); + }); + container.classList.add("collapsable"); + container.classList.add("collapsed"); + }; + }(); + + + scope.createActionButtons = function createActionButtons(container, actions, data){ + actions.forEach(function(action, i){ + var button = document.createElement("button"); + button.className = action.name; + button.textContent = browser.i18n.getMessage(action.name); + button.addEventListener("click", action.callback.bind(undefined, data)); + container.appendChild(button); + if (i % 3 === 2){ + container.appendChild(document.createElement("br")); + } + }); + }; + + + + scope.modalPrompt = function modalPrompt(messageText, defaultValue){ + message("open modal prompt"); + return new Promise(function(resolve, reject){ + document.body.innerHTML = ""; + document.body.appendChild(document.createTextNode(messageText)); + var input = document.createElement("input"); + input.value = defaultValue; + document.body.appendChild(input); + var button = document.createElement("button"); + button.textContent = "OK"; + button.addEventListener("click", function(){ + resolve(input.value); + message("modal prompt closed with value", input.value); + }); + document.body.appendChild(button); + }); + }; +}()); \ No newline at end of file diff --git a/pageAction/notification.js b/pageAction/notification.js new file mode 100644 index 0000000..b4d93a3 --- /dev/null +++ b/pageAction/notification.js @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +(function(){ + "use strict"; + + const {createCollapser, createActionButtons} = require("./gui"); + + const actions = []; + const addAction = function addAction(action){ + actions.push(action); + }; + + class Notification{ + constructor(data){ + Object.entries(data).forEach(function(entry){ + const [key, value] = entry; + this[key] = value; + }, this); + } + + node(){ + const node = document.createElement("li"); + + node.appendChild(document.createTextNode(this.timestamp.toLocaleString() + ": " + this.functionName + " ")); + if (this.dataURL){ + node.className = "notification collapsable collapsed"; + createCollapser(node); + const img = document.createElement("img"); + img.src = this.dataURL; + img.className = "collapsing"; + node.appendChild(img); + } + node.appendChild(this.actionsNode()); + + this.node = function(){ + return node; + }; + return node; + } + + actionsNode(){ + const node = document.createElement("div"); + + node.className = "actions"; + createActionButtons(node, actions, this); + + if (this.dataURL){ + node.classList.add("imageAvailable"); + } + + this.actionsNode = function(){ + return node; + }; + return node; + } + + update(){} + } + Notification.addAction = addAction; + window.scope.Notification = Notification; +}()); \ No newline at end of file diff --git a/pageAction/pageAction.css b/pageAction/pageAction.css index f52c345..adf699e 100644 --- a/pageAction/pageAction.css +++ b/pageAction/pageAction.css @@ -13,13 +13,51 @@ body { margin: 0; } -button.inspectImage { - display: none; -} -.imageAvailable button.inspectImage { - display: initial; +img { + display: block; + width: 100%; + box-sizing: border-box; } -img { - border: 1px solid black; +.notifications { + margin: 0; + padding: 0 0 0 1em; +} + +.collapsable { + position: relative; + margin: 0; +} +.collapsable.collapsed .collapser { + display: inline-block; + width: 20px; + height: 20px; + line-height: 20px; + box-sizing: border-box; +} +.collapsable .collapser { + cursor: pointer; + color: blue; + text-decoration: underline; +} +.collapsable .collapser .more { + display: none; +} +.collapsable.collapsed .collapser .more { + display: inline; +} +.collapsable.collapsed .collapser .less { + display: none; +} +.collapsable .collapser .less { + display: inline; +} +.collapsable.collapsed .collapsing { + height: 0px; +} +.collapsable .collapsing { + height: initial; + width: 100%; + box-sizing: border-box; + overflow: hidden; } \ No newline at end of file diff --git a/pageAction/pageAction.html b/pageAction/pageAction.html index a313a6f..c1597be 100644 --- a/pageAction/pageAction.html +++ b/pageAction/pageAction.html @@ -9,11 +9,15 @@ +
+ + + diff --git a/pageAction/pageAction.js b/pageAction/pageAction.js index 73582e2..aecea36 100644 --- a/pageAction/pageAction.js +++ b/pageAction/pageAction.js @@ -1,4 +1,3 @@ -/* jslint moz: true */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ @@ -6,9 +5,12 @@ Promise.all([ browser.tabs.query({active: true, currentWindow: true}), browser.storage.local.get().then(function(data){ + "use strict"; Object.keys(data).forEach(function(key){ settings[key] = data[key]; }); + settings.isStillDefault = false; + require("./logging").clearQueue(); return settings; }) ]).then(function(values){ @@ -16,146 +18,114 @@ Promise.all([ const [tabs, settings] = values; const {error, warning, message, notice, verbose, setPrefix: setLogPrefix} = require("./logging"); + const domainNotification = require("./domainNotification"); + const Notification = require("./Notification"); + const {createActionButtons, modalPrompt} = require("./gui"); setLogPrefix("page action script"); - - function modalPrompt(messageText, defaultValue){ - message("open modal prompt"); - return new Promise(function(resolve, reject){ - document.body.innerHTML = ""; - document.body.appendChild(document.createTextNode(messageText)); - var input = document.createElement("input"); - input.value = defaultValue; - document.body.appendChild(input); - var button = document.createElement("button"); - button.textContent = "OK"; - button.addEventListener("click", function(){ - resolve(input.value); - message("modal prompt closed with value", input.value); - }); - document.body.appendChild(button); - }); - } + + notice("create global action buttons"); + + createActionButtons( + document.getElementById("globalActions"), + [{ + name: "disableNotifications", + callback: function(){ + browser.storage.local.set({showNotifications: false}); + window.close(); + } + }], + undefined + ); if (!tabs.length){ throw new Error("noTabsFound"); } else if (tabs.length > 1){ - console.error(tabs); + error(tabs); throw new Error("tooManyTabsFound"); } const lists = require("./lists"); lists.updateAll(); const {parseErrorStack} = require("./callingStack"); - var actionsCallbacks = { - displayFullURL: function({url}){ - alert(url.href); + + verbose("registering domain actions"); + [ + { + name: "ignorelistDomain", + callback: function(domain){ + modalPrompt( + browser.i18n.getMessage("inputIgnoreDomain"), + domain + ).then(function(domain){ + if (domain){ + lists.appendTo("ignore", domain); + } + window.close(); + }); + } }, - displayCallingStack: function({errorStack}){ - alert(parseErrorStack(errorStack)); + { + name: "whitelistDomain", + callback: function(domain){ + modalPrompt( + browser.i18n.getMessage("inputWhitelistURL"), + domain + ).then(function(domain){ + if (domain){ + lists.appendTo("white", domain); + } + window.close(); + }); + } + } + ].forEach(function(domainAction){ + domainNotification.addAction(domainAction); + }); + + verbose("registering notification actions"); + [ + { + name: "displayFullURL", + callback: function({url}){ + alert(url.href); + } }, - inspectImage: function({dataURL}){ - document.body.innerHTML = ""; - var img = document.createElement("img"); - img.src = dataURL; - document.body.appendChild(img); + { + name: "displayCallingStack", + callback: function({errorStack}){ + alert(parseErrorStack(errorStack)); + } }, - ignorelistDomain: function({url}){ - var domain = url.host; - modalPrompt( - browser.i18n.getMessage("inputIgnoreDomain"), - url.host - ).then(function(domain){ - if (domain){ - lists.appendTo("ignore", domain); - } - window.close(); - }); - }, - whitelistURL: function({url}){ - modalPrompt( - browser.i18n.getMessage("inputWhitelistDomain"), - "^" + url.href.replace(/([\\\+\*\?\[\^\]\$\(\)\{\}\=\!\|\.])/g, "\\$1") + "$" - ).then(function(url){ - if (url){ - lists.appendTo("white", url); - } - window.close(); - }); - }, - whitelistDomain: function({url}){ - modalPrompt( - browser.i18n.getMessage("inputWhitelistURL"), - url.host - ).then(function(domain){ - if (domain){ - lists.appendTo("white", domain); - } - window.close(); - }); - }, - disableNotifications: function(notification){ - browser.storage.local.set({showNotifications: false}); - window.close(); - }, - - }; + { + name: "whitelistURL", + callback: function({url}){ + modalPrompt( + browser.i18n.getMessage("inputWhitelistDomain"), + "^" + url.href.replace(/([\\+*?[^\]$(){}=!|.])/g, "\\$1") + "$" + ).then(function(url){ + if (url){ + lists.appendTo("white", url); + } + window.close(); + }); + } + } + ].forEach(function(action){ + Notification.addAction(action); + }); var tab = tabs[0]; browser.runtime.onMessage.addListener(function(data){ if (Array.isArray(data["canvasBlocker-notifications"])){ message("got notifications"); - var ul = document.getElementById("prints"); data["canvasBlocker-notifications"].forEach(function(notification){ verbose(notification); - - var url = new URL(notification.url); - var li = document.createElement("li"); - li.className = "print"; - - - li.appendChild(document.createTextNode(" ")); - - var messageSpan = document.createElement("span"); - var messageParts = browser.i18n.getMessage(notification.messageId).split(/\{url\}/g); - messageSpan.appendChild(document.createTextNode(messageParts.shift())); - while (messageParts.length){ - var urlSpan = document.createElement("span"); - urlSpan.textContent = url.hostname; - urlSpan.title = url.href; - messageSpan.appendChild(urlSpan); - messageSpan.appendChild(document.createTextNode(messageParts.shift())); - } - messageSpan.title = notification.timestamp + ": " + notification.functionName; - li.appendChild(messageSpan); - - li.appendChild(document.createTextNode(" ")); - - var actions = document.createElement("span"); - actions.className = "actions"; - var data = {url, errorStack: notification.errorStack, notification, dataURL: notification.dataURL}; - Object.keys(actionsCallbacks).forEach(function(key, i){ - var button = document.createElement("button"); - button.className = key; - button.textContent = browser.i18n.getMessage(key); - button.addEventListener("click", function(){actionsCallbacks[key](data);}); - actions.appendChild(button); - if (i % 3 === 2){ - actions.appendChild(document.createElement("br")); - } - }); - if (notification.dataURL){ - actions.classList.add("imageAvailable"); - } - li.appendChild(actions); - - ul.appendChild(li); + notification.url = new URL(notification.url); + domainNotification(notification.url.hostname, notification.messageId).addNotification(new Notification(notification)); }); } }); - notice("clearing the display"); - var ul = document.getElementById("prints"); - ul.innerHTML = ""; message("request notifications from tab", tab.id); browser.tabs.sendMessage( tab.id, @@ -165,5 +135,6 @@ Promise.all([ ); notice("waiting for notifications"); }).catch(function(e){ - console.error(e); + "use strict"; + require("./logging").error(e); }); \ No newline at end of file