Cleanup of page action interface.

This commit is contained in:
kkapsner 2017-10-03 12:17:14 +02:00
parent f569a48b01
commit cde71f8a62
8 changed files with 424 additions and 124 deletions

View File

@ -8,6 +8,15 @@
"description": ""
},
"more": {
"message": "mehr",
"description": ""
},
"less": {
"message": "weniger",
"description": ""
},
"displayAdvancedSettings_title": {
"message": "Expertenmodus",
"description": ""

View File

@ -7,6 +7,15 @@
"message": "Changes the JS-API for modifying <canvas> to prevent Canvas-Fingerprinting.",
"description": ""
},
"more": {
"message": "more",
"description": ""
},
"less": {
"message": "less",
"description": ""
},
"displayAdvancedSettings_title": {
"message": "Expert mode",

View File

@ -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;
}());

64
pageAction/gui.js Normal file
View File

@ -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 = `<span class="more">${more}</span><span class="less">${less}</span>`;
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);
});
};
}());

View File

@ -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;
}());

View File

@ -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;
}

View File

@ -9,11 +9,15 @@
<ul id="prints">
<li>...</li>
</ul>
<div id="globalActions"></div>
<script src="../lib/defaultSettings.js"></script>
<script src="../lib/require.js"></script>
<script src="../lib/logging.js"></script>
<script src="../lib/lists.js"></script>
<script src="../lib/callingStack.js"></script>
<script src="gui.js"></script>
<script src="domainNotification.js"></script>
<script src="notification.js"></script>
<script src="pageAction.js"></script>
</body>
</html>

View File

@ -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);
});