1
0
mirror of https://github.com/kkapsner/CanvasBlocker synced 2025-01-03 10:31:54 +01:00

Added URL specific settings

For blockMode and showNotifications.

Fixes #148.
This commit is contained in:
kkapsner 2017-12-03 23:47:49 +01:00
parent 78a0ccc243
commit 01780da9f5
10 changed files with 618 additions and 78 deletions

View File

@ -169,6 +169,28 @@
"description": ""
},
"urlSettings_title": {
"message": "Seitenspezifische Einstellungen",
"description": ""
},
"urlSettings_description": {
"message": "",
"description": ""
},
"url_title": {
"message": "URL",
"description": ""
},
"url_description": {
"message": "",
"description": ""
},
"inputURL": {
"message": "Input domain or URL RegExp:",
"description": ""
},
"minFakeSize_description": {
"message": "Canvas, die eine kleiner oder gleich große Fläche als die hier angegebene Zahl haben, werden nicht vorgetäuscht. Dies ist ein Parameter, der die Detektion des Addons erschweren soll.\nACHTUNG: Dies verringert die Sicherheit des Addons. Deswegen wird stark empfohlen, diesen Wert nicht über 100 zu setzen.",
"description": ""

View File

@ -169,6 +169,28 @@
"description": ""
},
"urlSettings_title": {
"message": "Site specific settings",
"description": ""
},
"urlSettings_description": {
"message": "",
"description": ""
},
"url_title": {
"message": "URL",
"description": ""
},
"url_description": {
"message": "",
"description": ""
},
"inputURL": {
"message": "Input domain or URL RegExp:",
"description": ""
},
"minFakeSize_description": {
"message": "Canvas with a smaller or equal area than this number will not be faked. This is a parameter to prevent detection.\nCAUTION: This lowers the safety of the addon, therefore it is highly recommended not to set this value above 100.",
"description": ""

View File

@ -19,7 +19,8 @@
const logging = require("./logging");
scope.check = function check({url, errorStack}){
var match = checkBoth(errorStack, url, settings.blockMode).match(
url = new URL(url || "about:blank");
var match = checkBoth(errorStack, url, settings.get("blockMode", url)).match(
/^(block|allow|fake|ask)(|Readout|Everything|Context|Input|Internal)$/
);
if (match){
@ -50,7 +51,6 @@
function checkURL(url, blockMode){
logging.message("check url %s for block mode %s", url, blockMode);
url = new URL(url || "about:blank");
switch (url.protocol){
case "about:":
if (url.href === "about:blank"){

View File

@ -78,8 +78,8 @@
port.postMessage({"canvasBlocker-notify": data});
}
function prefs(name){
return settings[name];
function prefs(...args){
return settings.get(...args);
}

View File

@ -58,7 +58,7 @@
port.onMessage.addListener(function(data){
if (data.hasOwnProperty("canvasBlocker-notify")){
if (
settings.showNotifications &&
settings.get("showNotifications", url) &&
!lists.get("ignore").match(url)
){
browser.pageAction.show(port.sender.tab.id);

View File

@ -10,6 +10,25 @@
defaultValue: 1,
options: [0, 1, 25, 50, 75, 100]
},
{
name: "urlSettings",
defaultValue: [],
urlContainer: true,
entries: [
{name: "url", defaultValue: ""}
]
},
{
name: "urls",
defaultValue: [],
dynamic: true,
dependencies: ["urlSettings"],
getter: function(settings){
return settings.urlSettings.map(function(urlSetting){
return urlSetting.url;
});
}
},
{
name: "whiteList",
defaultValue: ""
@ -21,6 +40,7 @@
{
name: "blockMode",
defaultValue: "fakeReadout",
urlSpecific: true,
options: [
"blockReadout", "fakeReadout", "fakeInput", "askReadout", null,
"blockEverything", "block", "ask", "allow", "allowEverything"
@ -84,7 +104,8 @@
},
{
name: "showNotifications",
defaultValue: true
defaultValue: true,
urlSpecific: true
},
{
name: "storeImageForInspection",

View File

@ -31,26 +31,48 @@
});
}());
const settingDefinitions = require("./settingDefinitions.js");
const definitionsByName = {};
const defaultSymbol = "";
const eventHandler = {any: []};
const eventHandler = {any: {}};
eventHandler.any[defaultSymbol] = [];
eventHandler.all = eventHandler.any;
const settings = {};
let urlContainer;
settingDefinitions.forEach(function(settingDefinition){
var name = settingDefinition.name;
settings[name] = settingDefinition.defaultValue;
eventHandler[name] = [];
settingDefinition.on = function on(callback){
scope.on(name, callback);
};
settingDefinition.invalid = function invalid(newValue){
if (settingDefinition.fixed){
function isDefinitionInvalid(settingDefinition, newValue){
if (newValue === undefined && settingDefinition.optional){
return false;
}
else if (settingDefinition.fixed){
return "fixed";
}
else if ((typeof newValue) !== (typeof settingDefinition.defaultValue)){
return "wrongType";
}
else if (Array.isArray(settingDefinition.defaultValue)){
if (!Array.isArray(newValue)){
return "wrongType";
}
var entriesInvalid = newValue.reduce(function(v, entry){
v = v || settingDefinition.entries.reduce(function(v, entryDefinition){
return v || isDefinitionInvalid(entryDefinition, entry[entryDefinition.name]);
}, false);
if (!v){
if (Object.keys(entry).some(function(key){
return !settingDefinition.entries.some(function(entryDefinition){
return key === entryDefinition.name;
});
})){
return "noOption";
}
}
return v;
}, false);
if (entriesInvalid){
return entriesInvalid;
}
}
else if (
settingDefinition.options &&
!settingDefinition.options.includes(newValue)
@ -58,12 +80,45 @@
return "noOption";
}
return false;
}
function createGetter(settingDefinition){
if (settingDefinition.dynamic){
return function getValue(){
return settingDefinition.getter(scope);
};
settingDefinition.get = function getValue(){
return settings[name];
}
else if (settingDefinition.urlSpecific){
return function getValue(url){
if (url){
var matching = urlContainer.get().filter(function(urlSetting){
return urlSetting.hasOwnProperty(settingDefinition.name);
}).filter(function(urlSetting){
return urlSetting.match(url);
});
if (matching.length){
return matching[0][settingDefinition.name];
}
}
return settings[settingDefinition.name];
};
settingDefinition.set = function setValue(newValue){
logging.verbose("New value for %s:", name, newValue);
}
else {
return function getValue(){
return settings[settingDefinition.name];
};
}
}
function createSetter(settingDefinition){
if (settingDefinition.dynamic){
return function setValue(newValue){
settingDefinition.setter(scope);
};
}
else {
const name = settingDefinition.name;
const isValid = function isValid(newValue){
var invalid = settingDefinition.invalid(newValue);
if (invalid){
if (invalid === "fixed"){
@ -78,17 +133,138 @@
else {
logging.warning("Unknown invalid state:", invalid);
}
return false;
}
else {
return true;
};
const storeValue = function storeValue(newValue){
settings[name] = newValue;
if (!settingDefinition.transient){
var storeObject = {};
storeObject[name] = newValue;
browser.storage.local.set(storeObject);
}
};
if (settingDefinition.urlSpecific){
return function setValue(newValue, url){
logging.verbose("New value for %s:", name, newValue);
if (isValid(newValue)){
if (url){
var urlContainerValue = urlContainer.get();
var matching = urlContainerValue.filter(function(urlSetting){
return urlSetting.match(url);
});
if (!matching.length){
let newEntry = {url};
newEntry[settingDefinition.name] = newValue;
urlContainerValue.push(newEntry);
matching = [newEntry];
}
matching[0][settingDefinition.name] = newValue;
urlContainer.set(urlContainerValue);
}
else {
storeValue(newValue);
}
}
};
}
else {
return function setValue(newValue){
logging.verbose("New value for %s:", name, newValue);
if (isValid(newValue)){
storeValue(newValue);
}
};
}
}
}
function createResetter(settingDefinition){
if (settingDefinition.dynamic){
return function(){};
}
else {
const name = settingDefinition.name;
let reset = function(){
settings[name] = settingDefinition.defaultValue;
browser.storage.local.remove(name);
};
if (settingDefinition.urlSpecific){
return function(url){
if (url){
var urlContainerValue = urlContainer.get();
var matching = urlContainerValue.filter(function(urlSetting){
return urlSetting.match(url);
});
if (matching.length){
delete matching[0][name];
if (Object.keys(matching[0]).every(function(key){return key === "url";})){
urlContainerValue = urlContainerValue.filter(function(urlSetting){
return urlSetting !== matching[0];
});
}
urlContainer.set(urlContainerValue);
}
}
else {
reset();
}
};
}
else {
return reset;
}
}
}
settingDefinitions.forEach(function(settingDefinition){
if (settingDefinition.urlContainer){
urlContainer = settingDefinition;
settingDefinition.refresh = function(){
settingDefinition.set(settingDefinition.get());
};
}
var name = settingDefinition.name;
definitionsByName[name] = settingDefinition;
settings[name] = settingDefinition.defaultValue;
eventHandler[name] = {};
settingDefinition.on = function on(callback, url){
if (!settingDefinition.dynamic){
scope.on(name, callback, url);
}
if (settingDefinition.dependencies){
settingDefinition.dependencies.forEach(function(dependency){
scope.on(dependency, function(){
callback({name, newValue: settingDefinition.get()});
}, url);
});
}
};
settingDefinition.invalid = function invalid(newValue){
return isDefinitionInvalid(settingDefinition, newValue);
};
settingDefinition.get = createGetter(settingDefinition);
settingDefinition.set = createSetter(settingDefinition);
settingDefinition.reset = createResetter(settingDefinition);
if (settingDefinition.urlSpecific){
if (!urlContainer){
logging.error("Unable to use url specific settings without url-container");
}
else {
settingDefinition.urlContainer = urlContainer;
let entry = Object.create(settingDefinition);
entry.optional = true;
urlContainer.entries.push(entry);
}
}
Object.defineProperty(
scope,
name,
@ -101,31 +277,57 @@
});
scope.getDefinition = function(name){
var foundDefinitions = settingDefinitions.filter(function(settingDefinition){
return name === settingDefinition.name;
});
if (foundDefinitions.length){
return Object.create(foundDefinitions[0]);
var foundDefinition = definitionsByName[name];
if (foundDefinition){
return Object.create(foundDefinition);
}
else {
return undefined;
}
};
scope.set = function(name, ...args){
var foundDefinition = definitionsByName[name];
if (foundDefinition){
return foundDefinition.set(...args);
}
else {
logging.error("Try to set unkown setting:", name);
}
};
scope.get = function(name, ...args){
var foundDefinition = definitionsByName[name];
if (foundDefinition){
return foundDefinition.get(...args);
}
else {
logging.error("Try to get unkown setting:", name);
}
};
scope.forEach = function forEachSetting(...args){
settingDefinitions.map(function(settingDefinition){
settingDefinitions.filter(function(settingDefinition){
return !settingDefinition.dynamic;
}).map(function(settingDefinition){
return Object.create(settingDefinition);
}).forEach(...args);
};
scope.on = function onSettingsChange(name, callback){
scope.on = function onSettingsChange(name, callback, url){
if (Array.isArray(name)){
name.forEach(function(name){
onSettingsChange(name, callback);
onSettingsChange(name, callback, url);
});
}
else {
if (eventHandler.hasOwnProperty(name)){
eventHandler[name].push(callback);
if (!url){
url = defaultSymbol;
}
if (!eventHandler[name].hasOwnProperty(url)){
eventHandler[name][url] = [];
}
eventHandler[name][url].push(callback);
}
else {
logging.warning("Unable to register event handler for unknown setting", name);
@ -133,28 +335,136 @@
}
};
const resetSymbol = Symbol("reset");
function changeValue(name, newValue){
var settingDefinition = scope.getDefinition(name);
var oldValue = settings[name];
if (newValue === resetSymbol){
newValue = settingDefinition.defaultValue;
}
settings[name] = newValue;
(eventHandler[name] || []).forEach(function(callback){
((eventHandler[name] || {})[defaultSymbol] || []).forEach(function(callback){
callback({name, newValue, oldValue});
});
if (settingDefinition.urlSpecific){
urlContainer.get().forEach(function(entry){
if (!entry.hasOwnProperty(name)){
((eventHandler[name] || {})[entry.url] || []).forEach(function(callback){
callback({name, newValue, oldValue, url: entry.url});
});
}
});
}
}
logging.verbose("registering storage onchange listener");
browser.storage.onChanged.addListener(function(changes, area){
if (area === "local"){
logging.notice("settings changed", changes);
var delayedChange = [];
Object.entries(changes).forEach(function(entry){
const [name, change] = entry;
if (urlContainer && name === urlContainer.name){
// changes in the url container have to trigger after the other changes
delayedChange.push(entry);
}
else {
if (change.hasOwnProperty("newValue")){
changeValue(name, change.newValue);
}
else {
changeValue(name, resetSymbol);
}
}
});
eventHandler.any.forEach(function(callback){
delayedChange.forEach(function(entry){
const [name, change] = entry;
if (change.hasOwnProperty("newValue")){
changeValue(name, change.newValue);
}
else {
changeValue(name, resetSymbol);
}
});
eventHandler.any[""].forEach(function(callback){
callback();
});
}
});
if (urlContainer){
urlContainer.on(function({newValue, oldValue}){
newValue.forEach(function(urlSetting){
var regExp;
var domain = !!urlSetting.url.match(/^[\w.]+$/);
if (domain){
regExp = new RegExp(
"(?:^|\\.)" + urlSetting.url.replace(/([\\+*?[^\]$(){}=!|.])/g, "\\$1") + "\\.?$",
"i"
);
}
else {
regExp = new RegExp(urlSetting.url, "i");
}
const match = function(url){
if (!url){
return false;
}
else if (
url instanceof String ||
(typeof url) === "string"
){
return url === urlSetting.url;
}
else if (domain){
return (url.hostname || "").match(regExp);
}
else {
return url.href.match(regExp);
}
};
Object.defineProperty(
urlSetting,
"match",
{
enumerable: false,
writable: true,
configurable: true,
value: match
}
);
});
var newUrls = newValue.map(function(entry){return entry.url;});
var oldUrls = oldValue.map(function(entry){return entry.url;});
var matching = {};
newUrls.forEach(function(url, i){
matching[url] = {new: i, old: oldUrls.indexOf(url)};
});
oldUrls.forEach(function(url, i){
if (!matching[url]){
matching[url] = {new: -1, old: i};
}
});
Object.keys(matching).forEach(function(url){
var oldEntry = oldValue[matching[url].old] || {};
var newEntry = newValue[matching[url].new] || {};
urlContainer.entries.forEach(function(settingDefinition){
var name = settingDefinition.name;
var oldValue = oldEntry[name];
var newValue = newEntry[name];
if (oldValue !== newValue){
((eventHandler[name] || {})[url] || []).forEach(function(callback){
callback({name, newValue, oldValue, url});
});
}
});
});
});
}
const settingsMigration = {
validVersions: [undefined, 0.1, 0.2],
transitions: {
@ -170,6 +480,47 @@
if (oldStorage.hasOwnProperty("askOnlyOnce")){
newStorage.askOnlyOnce = oldStorage.askOnlyOnce? "individual": "no";
}
return newStorage;
},
0.2: function(oldStorage){
var newStorage = {
storageVersion: 0.3,
urlSettings: (
oldStorage.urlSettings &&
Array.isArray(oldStorage.urlSettings)
)? oldStorage.urlSettings: []
};
var urlSettings = {};
(oldStorage.blackList || "").split(",").forEach(function(url){
var entry = urlSettings[url];
if (!entry){
entry = {url, blockMode: "block"};
urlSettings[url] = entry;
newStorage.urlSettings.push(entry);
}
});
(oldStorage.whiteList || "").split(",").forEach(function(url){
var entry = urlSettings[url];
if (!entry){
entry = {url, blockMode: "allow"};
urlSettings[url] = entry;
newStorage.urlSettings.push(entry);
}
});
(oldStorage.ignoreList || "").split(",").forEach(function(url){
var entry = urlSettings[url];
if (!entry){
entry = {url, showNotifications: false};
urlSettings[url] = entry;
newStorage.urlSettings.push(entry);
}
else {
entry.showNotifications = false;
}
});
return newStorage;
}
}
@ -210,7 +561,18 @@
logging.notice("Changed settings:", toChange);
browser.storage.local.set(toChange);
}
var delayedChange = [];
Object.entries(storage).forEach(function(entry){
const [name, value] = entry;
if (urlContainer && name === urlContainer.name){
// changes in the url container have to trigger after the other changes
delayedChange.push(entry);
}
else {
changeValue(name, value);
}
});
delayedChange.forEach(function(entry){
const [name, value] = entry;
changeValue(name, value);
});

View File

@ -1,6 +1,7 @@
.settings {
width: 100%;
border-spacing: 0;
border-collapse: collapse;
}
.settings.displayDescriptions {
table-layout: fixed;
@ -71,3 +72,44 @@ input[type=""], input[type="text"], input[type="number"], select {
*.multiple4 {
width: 25% !important;
}
.urlValues {
padding-right: 1em;
position: relative;
}
.urlValues.collapsed table {
display: none;
}
.urlValues.expanded table {
display: block;
margin: 0.5em auto;
}
.urlValues table caption {
text-align: center;
font-weight: bold;
white-space: nowrap;
}
.urlValues table td {
vertical-align: middle;
}
.urlValues table .reset, .urlValues table .add, .urlValues table .url {
cursor: pointer;
}
.urlValues .collapser {
position: absolute;
top: 0;
right: 0;
text-align: center;
width: 1em;
height: 1em;
cursor: pointer;
}
.urlValues.collapsed .collapser::after {
content: "\25B6";
}
.urlValues.expanded .collapser::after {
content: "\25BC";
}
.urlValues .notSpecifiedForUrl {
opacity: 0.5;
}

View File

@ -66,12 +66,11 @@
input.value = value;
return input;
},
updateCallback: function(input, setting){
input.value = setting.get();
updateCallback: function(input, value){
input.value = value;
return input.value;
},
changeCallback: function(input, setting){
setting.set(parseFloat(input.value));
getValue: function(input){
return parseFloat(input.value);
}
},
@ -81,12 +80,11 @@
input.value = value;
return input;
},
updateCallback: function(input, setting){
input.value = setting.get();
updateCallback: function(input, value){
input.value = value;
return input.value;
},
changeCallback: function(input, setting){
setting.set(input.value);
getValue: function(input){
return input.value;
}
},
@ -97,38 +95,110 @@
input.style.display = "inline";
return input;
},
updateCallback: function(input, setting){
input.checked = setting.get();
updateCallback: function(input, value){
input.checked = value;
return input.checked;
},
changeCallback: function(input, setting){
setting.set(input.checked);
getValue: function(input){
return input.checked;
}
}
},
object: false
};
function createInput(setting){
function createInput(setting, url = ""){
var type = inputTypes[typeof setting.defaultValue];
var input;
if (setting.options){
input = createSelect(setting);
}
else {
input = document.createElement("input");
if (type){
input = document.createElement("input");
type.input(input, setting.defaultValue);
}
}
if (type){
setting.on(function(){type.updateCallback(input, setting);});
setting.on(function(){type.updateCallback(input, setting.get(url));}, url);
input.addEventListener("change", function(){
var value = type.changeCallback(input, setting);
var value = type.getValue(input);
setting.set(value, url);
logging.message("changed setting", setting.name, ":", value);
});
}
return input;
if (setting.urlSpecific && url === ""){
let container = document.createElement("div");
container.className = "urlValues collapsed";
container.appendChild(input);
var collapser = document.createElement("span");
collapser.classList.add("collapser");
container.appendChild(collapser);
collapser.addEventListener("click", function(){
container.classList.toggle("collapsed");
container.classList.toggle("expanded");
});
let urlTable = document.createElement("table");
let caption = document.createElement("caption");
caption.textContent = browser.i18n.getMessage(setting.urlContainer.name + "_title");
urlTable.appendChild(caption);
let body = document.createElement("tbody");
urlTable.appendChild(body);
let foot = document.createElement("tfoot");
let footRow = document.createElement("tr");
let footCell = document.createElement("td");
footCell.colSpan = 3;
let footPlus = document.createElement("span");
footPlus.classList.add("add");
footPlus.textContent = "+";
footPlus.addEventListener("click", function(){
var url = prompt(browser.i18n.getMessage("inputURL")).trim();
if (url){
setting.set(setting.get(url), url);
}
});
footCell.appendChild(footPlus);
footRow.appendChild(footCell);
foot.appendChild(footRow);
urlTable.appendChild(foot);
container.appendChild(urlTable);
setting.urlContainer.on(function({newValue}){
body.innerHTML = "";
newValue.forEach(function(entry){
let row = document.createElement("tr");
let urlCell = document.createElement("td");
urlCell.classList.add("url");
urlCell.addEventListener("click", function(){
var url = prompt(browser.i18n.getMessage("inputURL"), entry.url).trim();
if (url){
entry.url = url;
setting.urlContainer.refresh();
}
});
urlCell.textContent = entry.url;
row.appendChild(urlCell);
let input = createInput(setting, entry.url);
type.updateCallback(input, setting.get(entry.url));
if (!entry.hasOwnProperty(setting.name)){
input.classList.add("notSpecifiedForUrl");
}
let inputCell = document.createElement("td");
inputCell.appendChild(input);
row.appendChild(inputCell);
let clearCell = document.createElement("td");
clearCell.className = "reset";
clearCell.textContent = "\xD7";
clearCell.addEventListener("click", function(){
setting.reset(entry.url);
});
row.appendChild(clearCell);
body.appendChild(row);
});
});
return container;
}
return input || document.createElement("span");
}
function createButton(setting){
@ -158,7 +228,7 @@
interaction = createInput(setting);
}
interaction.className = "setting";
interaction.classList.add("setting");
interaction.dataset.storageName = setting.name;
interaction.dataset.storageType = typeof setting.defaultValue;

View File

@ -8,6 +8,7 @@ Version 0.4.3:
new features:
- reset settings
- new white random generator - creates output similar to Tor browser
- blockMode and showNotifications can now be chosen url specific
fixes:
- page action was not always showing