mirror of
https://github.com/kkapsner/CanvasBlocker
synced 2024-12-22 21:00:23 +01:00
468 lines
12 KiB
JavaScript
468 lines
12 KiB
JavaScript
/* 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(require){
|
|
"use strict";
|
|
|
|
let scope;
|
|
if ((typeof exports) !== "undefined"){
|
|
scope = exports;
|
|
}
|
|
else {
|
|
scope = require.register("./settings", {});
|
|
}
|
|
|
|
const logging = require("./logging");
|
|
const extension = require("./extension");
|
|
const settingDefinitions = require("./settingDefinitions");
|
|
const settingContainers = require("./settingContainers");
|
|
const definitionsByName = {};
|
|
const defaultSymbol = "";
|
|
|
|
const eventHandler = {any: {}};
|
|
eventHandler.any[defaultSymbol] = [];
|
|
eventHandler.all = eventHandler.any;
|
|
const settings = {};
|
|
|
|
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";
|
|
}
|
|
const 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)
|
|
){
|
|
return "noOption";
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function createGetter(settingDefinition){
|
|
if (settingDefinition.dynamic){
|
|
return function getValue(){
|
|
return settingDefinition.getter(scope);
|
|
};
|
|
}
|
|
else if (settingDefinition.urlSpecific){
|
|
return function getValue(url){
|
|
if (url){
|
|
const match = settingContainers.getUrlValueContainer(settingDefinition.name, url);
|
|
if (match){
|
|
return match[settingDefinition.name];
|
|
}
|
|
}
|
|
return settings[settingDefinition.name];
|
|
};
|
|
}
|
|
else {
|
|
return function getValue(){
|
|
return settings[settingDefinition.name];
|
|
};
|
|
}
|
|
}
|
|
|
|
function getDefaultValue(settingDefinition){
|
|
let defaultValue = settingDefinition.defaultValue;
|
|
if ((typeof defaultValue) === "object"){
|
|
if (Array.isArray(defaultValue)){
|
|
return defaultValue.slice();
|
|
}
|
|
else {
|
|
return Object.create(defaultValue);
|
|
}
|
|
}
|
|
return defaultValue;
|
|
}
|
|
|
|
function createSetter(settingDefinition){
|
|
if (settingDefinition.dynamic){
|
|
return function setValue(newValue){
|
|
settingDefinition.setter(scope, newValue);
|
|
};
|
|
}
|
|
else {
|
|
const name = settingDefinition.name;
|
|
const isValid = function isValid(newValue){
|
|
const invalid = settingDefinition.invalid(newValue);
|
|
if (invalid){
|
|
if (invalid === "fixed"){
|
|
logging.warning("Trying to set the fixed setting", name, ":", newValue);
|
|
}
|
|
else if (invalid === "wrongType"){
|
|
logging.warning("Wrong type provided for setting", name, ":", newValue);
|
|
}
|
|
else if (invalid === "noOption"){
|
|
logging.warning("Provided value outside specified options for ", name, ":", newValue);
|
|
}
|
|
else {
|
|
logging.warning("Unknown invalid state:", invalid);
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
const storeValue = async function storeValue(newValue){
|
|
logging.verbose("Trying to store new value for %s", name, newValue);
|
|
settings[name] = newValue;
|
|
if (!settingDefinition.transient){
|
|
const storeObject = {};
|
|
storeObject[name] = newValue;
|
|
try {
|
|
await browser.storage.local.set(storeObject);
|
|
logging.verbose("New value stored for %s:", name, newValue);
|
|
}
|
|
catch (error){
|
|
logging.error("Unable to store new value for %s:", name, newValue, error);
|
|
throw error;
|
|
}
|
|
}
|
|
else {
|
|
logging.warning("Transient setting %s cannot be stored.", name);
|
|
throw "Transient setting " + name + " cannot be stored.";
|
|
}
|
|
};
|
|
|
|
if (settingDefinition.urlSpecific){
|
|
return function setValue(newValue, url){
|
|
logging.verbose("New value for %s (%s):", name, url, newValue);
|
|
if (isValid(newValue)){
|
|
if (url){
|
|
return settingContainers.setUrlValue(name, newValue, url);
|
|
}
|
|
else {
|
|
return storeValue(newValue);
|
|
}
|
|
}
|
|
else{
|
|
logging.warning("Invalid value for %s (%s):", name, url, newValue);
|
|
return Promise.reject("Invalid value for " + name + " (" + url + "): " + newValue);
|
|
}
|
|
};
|
|
}
|
|
else {
|
|
return function setValue(newValue){
|
|
logging.verbose("New value for %s:", name, newValue);
|
|
if (isValid(newValue)){
|
|
return storeValue(newValue);
|
|
}
|
|
else{
|
|
logging.warning("Invalid value for %s:", name, newValue);
|
|
return Promise.reject("Invalid value for " + name + ": " + newValue);
|
|
}
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
function createResetter(settingDefinition){
|
|
if (settingDefinition.dynamic){
|
|
return function(){};
|
|
}
|
|
else {
|
|
const name = settingDefinition.name;
|
|
let reset = function(){
|
|
settings[name] = getDefaultValue(settingDefinition);
|
|
browser.storage.local.remove(name);
|
|
};
|
|
if (settingDefinition.urlSpecific){
|
|
return function(url){
|
|
if (url){
|
|
settingContainers.resetUrlValue(name, url);
|
|
}
|
|
else {
|
|
reset();
|
|
}
|
|
};
|
|
}
|
|
else {
|
|
return reset;
|
|
}
|
|
}
|
|
}
|
|
|
|
scope.on = function onSettingsChange(name, callback, url){
|
|
if (Array.isArray(name)){
|
|
name.forEach(function(name){
|
|
onSettingsChange(name, callback, url);
|
|
});
|
|
}
|
|
else {
|
|
if (eventHandler.hasOwnProperty(name)){
|
|
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);
|
|
}
|
|
}
|
|
};
|
|
|
|
settingDefinitions.forEach(function(settingDefinition){
|
|
const name = settingDefinition.name;
|
|
definitionsByName[name] = settingDefinition;
|
|
if (typeof settingDefinition.defaultValue === "function"){
|
|
settingDefinition.defaultValue = settingDefinition.defaultValue();
|
|
}
|
|
settings[name] = getDefaultValue(settingDefinition);
|
|
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 (!settingContainers.urlContainer){
|
|
logging.error("Unable to use url specific settings without url-container");
|
|
}
|
|
else {
|
|
settingDefinition.urlContainer = settingContainers.urlContainer;
|
|
let entry = Object.create(settingDefinition);
|
|
entry.optional = true;
|
|
settingContainers.urlContainer.entries.push(entry);
|
|
}
|
|
}
|
|
|
|
Object.defineProperty(
|
|
scope,
|
|
name,
|
|
{
|
|
get: settingDefinition.get,
|
|
set: settingDefinition.set,
|
|
enumerable: true
|
|
}
|
|
);
|
|
|
|
settingContainers.check(settingDefinition);
|
|
});
|
|
|
|
scope.getDefinition = function(name){
|
|
const foundDefinition = definitionsByName[name];
|
|
if (foundDefinition){
|
|
return Object.create(foundDefinition);
|
|
}
|
|
else {
|
|
return undefined;
|
|
}
|
|
};
|
|
|
|
scope.getContainers = function(){
|
|
return {
|
|
url: Object.create(settingContainers.urlContainer),
|
|
hide: Object.create(settingContainers.hideContainer),
|
|
expand: Object.create(settingContainers.expandContainer)
|
|
};
|
|
};
|
|
|
|
scope.set = function(name, ...args){
|
|
const foundDefinition = definitionsByName[name];
|
|
if (foundDefinition){
|
|
return foundDefinition.set(...args);
|
|
}
|
|
else {
|
|
logging.error("Try to set unknown setting:", name);
|
|
return Promise.reject("Try to set unknown setting: " + name);
|
|
}
|
|
};
|
|
scope.get = function(name, ...args){
|
|
const foundDefinition = definitionsByName[name];
|
|
if (foundDefinition){
|
|
return foundDefinition.get(...args);
|
|
}
|
|
else {
|
|
logging.error("Try to get unknown setting:", name);
|
|
return undefined;
|
|
}
|
|
};
|
|
|
|
scope.forEach = function forEachSetting(...args){
|
|
settingDefinitions.filter(function(settingDefinition){
|
|
return !settingDefinition.dynamic;
|
|
}).map(function(settingDefinition){
|
|
return Object.create(settingDefinition);
|
|
}).forEach(...args);
|
|
};
|
|
|
|
const resetSymbol = Symbol("reset");
|
|
function changeValue(name, newValue){
|
|
const settingDefinition = scope.getDefinition(name);
|
|
if (settingDefinition){
|
|
const oldValue = settings[name];
|
|
if (newValue === resetSymbol){
|
|
newValue = getDefaultValue(settingDefinition);
|
|
}
|
|
settings[name] = newValue;
|
|
((eventHandler[name] || {})[defaultSymbol] || []).forEach(function(callback){
|
|
callback({name, newValue, oldValue});
|
|
});
|
|
|
|
if (settingDefinition.urlSpecific){
|
|
settingContainers.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);
|
|
const delayedChange = [];
|
|
Object.entries(changes).forEach(function(entry){
|
|
const [name, change] = entry;
|
|
if (settingContainers.urlContainer && name === settingContainers.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);
|
|
}
|
|
}
|
|
});
|
|
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();
|
|
});
|
|
}
|
|
});
|
|
|
|
settingContainers.initializeUrlContainer(eventHandler);
|
|
|
|
logging.verbose("loading settings");
|
|
let initialized = false;
|
|
scope.isInitialized = function(){
|
|
return initialized;
|
|
};
|
|
const initEvents = [];
|
|
scope.init = function(storage){
|
|
if (initialized){
|
|
return false;
|
|
}
|
|
initialized = true;
|
|
logging.message("settings loaded");
|
|
if (require("./extension").inBackgroundScript){
|
|
const settingsMigration = require("./settingsMigration");
|
|
settingsMigration.check(
|
|
storage,
|
|
{settings, logging, changeValue, urlContainer: settingContainers.urlContainer}
|
|
);
|
|
}
|
|
const delayedChange = [];
|
|
Object.entries(storage).forEach(function(entry){
|
|
const [name, value] = entry;
|
|
if (settingContainers.urlContainer && name === settingContainers.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);
|
|
});
|
|
changeValue("isStillDefault", false);
|
|
|
|
initEvents.forEach(function(callback){callback();});
|
|
return true;
|
|
};
|
|
if (require.exists("./settingsData")){
|
|
scope.init(require("./settingsData"));
|
|
scope.loaded = Promise.resolve(false);
|
|
}
|
|
else {
|
|
scope.loaded = browser.storage.local.get().then(scope.init);
|
|
}
|
|
scope.onloaded = function(callback){
|
|
if (scope.isStillDefault){
|
|
initEvents.push(callback);
|
|
}
|
|
else {
|
|
callback();
|
|
}
|
|
};
|
|
scope.forceLoad = function(){
|
|
while (settings.isStillDefault){
|
|
extension.waitSync("to wait for settings");
|
|
logging.message("settings still default?", settings.isStillDefault);
|
|
}
|
|
};
|
|
scope.startupReset = function(){
|
|
scope.forEach(function(definition){
|
|
if (definition.resetOnStartup){
|
|
definition.set(getDefaultValue(definition));
|
|
}
|
|
});
|
|
};
|
|
Object.seal(scope);
|
|
|
|
logging.setSettings(scope);
|
|
}(require)); |