CanvasBlocker/options/options.js

610 lines
18 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(){
"use strict";
const extension = require("../lib/extension");
const logging = require("../lib/logging");
logging.setPrefix("options page");
const optionsGui = require("./optionsGui");
const settings = require("../lib/settings");
const settingsDisplay = require("../lib/settingsDisplay");
const search = require("../lib/search");
const settingStrings = require("../lib/settingStrings");
const searchParameters = new URLSearchParams(window.location.search);
const settingsMigration = require("../lib/settingsMigration");
require("./theme").init("options");
const modal = require("../lib/modal");
const mobile = require("../lib/mobile");
const callbacks = {
openNavigatorSettings: function(){
logging.verbose("open navigator settings");
window.open("navigator.html", "_blank");
},
showReleaseNotes: function(){
logging.verbose("open release notes");
window.open("../releaseNotes.txt", "_blank");
},
clearPersistentRnd: function(){
logging.message("clear persistent rnd storage");
logging.notice("empty storage");
settings.persistentRndStorage = "";
logging.notice("send message to main script");
extension.message.send({"canvasBlocker-clear-domain-rnd": true});
},
clearPersistentRndForContainer: async function(){
const identities = await browser.contextualIdentities.query({});
const identity = await modal.select(
extension.getTranslation("clearPersistentRndForContainer_title"),
identities.map(function(identity){
return {
name: `${identity.name} (${identity.cookieStoreId})`,
object: identity
};
})
);
extension.message.send({"canvasBlocker-clear-container-rnd": identity.cookieStoreId});
},
inspectSettings: function(){
logging.verbose("open settings inspection");
window.open("export.html", "_blank");
},
openSettingSanitation: function(){
logging.verbose("open settings sanitation");
window.open("sanitize.html", "_blank");
},
openSettingPresets: function(){
logging.verbose("open setting presets");
window.open("presets.html", "_blank");
},
saveSettings: function(){
logging.verbose("save settings");
const data = {};
settings.forEach(function(def){
data[def.name] = def.get();
});
const blob = new Blob([JSON.stringify(data, null, "\t")], {type: "application/json"});
const link = document.createElement("a");
link.href = window.URL.createObjectURL(blob);
link.target = "_blank";
const now = new Date();
function format(number, digits){
const str = number.toFixed(0);
return "0".repeat(digits - str.length) + str;
}
link.download = "CanvasBlocker-settings_" +
format(now.getFullYear(), 4) + "-" +
format(now.getMonth() + 1, 2) + "-" +
format(now.getDate(), 2) + "_" +
format(now.getHours(), 2) + "-" +
format(now.getMinutes(), 2) + "-" +
format(now.getSeconds(), 2) +
".json";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
},
inspectWhitelist: function(){
logging.verbose("open whitelist inspection");
window.open("whitelist.html", "_blank");
},
loadSettings: async function(){
logging.verbose("load settings");
const text = await new Promise(function(resolve, reject){
const input = document.createElement("input");
input.type = "file";
input.addEventListener("change", function(){
if (this.files.length){
const reader = new FileReader();
reader.onload = function(){
resolve(this.result);
};
reader.onerror = function(error){
reject(error);
};
reader.readAsText(this.files[0]);
}
});
input.click();
});
let json = JSON.parse(text);
while (settingsMigration.transitions.hasOwnProperty(json.storageVersion)){
let oldVersion = json.storageVersion;
json = settingsMigration.transitions[json.storageVersion](json);
if (oldVersion === json.storageVersion){
break;
}
}
const keys = Object.keys(json);
keys.forEach(function(key){
const setting = settings.getDefinition(key);
if (!settings){
throw new Error("Unknown setting " + key + ".");
}
if (!setting.fixed && setting.invalid(json[key])){
throw new Error("Invalid value " + json[key] + " for " + key + ".");
}
});
keys.forEach(function(key){
settings[key] = json[key];
});
},
resetSettings: async function(){
try {
const clear = await modal.confirm(
extension.getTranslation("resetSettings_confirm"),
{
node: this,
selector: ".settingRow .content"
}
);
if (clear){
await browser.storage.local.clear();
await browser.storage.local.set({storageVersion: settings.storageVersion});
if (await mobile.isMobile()){
await mobile.applyMobileDefaults();
}
}
}
catch (error){
logging.warning("Unable to reset settings:", error);
}
}
};
browser.tabs.getCurrent().then(function(tab){
document.querySelector("head title").textContent = extension.getTranslation("options_title");
let head = document.createElement("header");
document.body.insertBefore(head, document.body.firstChild);
if (tab.url === window.location.href){
let heading = document.createElement("h1");
heading.textContent = extension.getTranslation("options");
head.appendChild(heading);
let introduction = document.createElement("div");
introduction.textContent = extension.getTranslation("optionsIntroduction");
head.appendChild(introduction);
if (searchParameters.has("notice")){
let noticeName = searchParameters.get("notice") + "Notice";
let notice = extension.getTranslation(noticeName);
if (notice){
let bookmarkingNotice = document.createElement("div");
bookmarkingNotice.className = noticeName + " bookmarkNotice";
bookmarkingNotice.textContent = notice;
const dontShowAgain = document.createElement("label");
dontShowAgain.className = "dontShowOptionsOnUpdate";
const dontShowAgainInput = document.createElement("input");
dontShowAgainInput.type = "checkbox";
settings.onloaded(function(){
dontShowAgainInput.checked = settings.dontShowOptionsOnUpdate;
});
dontShowAgainInput.addEventListener("change", function(){
settings.dontShowOptionsOnUpdate = this.checked;
});
dontShowAgain.appendChild(dontShowAgainInput);
dontShowAgain.appendChild(
document.createTextNode(
" " + extension.getTranslation("dontShowOptionsOnUpdate")
)
);
bookmarkingNotice.appendChild(dontShowAgain);
head.appendChild(bookmarkingNotice);
const newUrl = new URL(window.location.href);
newUrl.search = "";
window.history.pushState({}, "", newUrl.href);
}
}
document.body.classList.add("standalone");
}
else {
const linkDiv = document.createElement("div");
linkDiv.className = "optionsLink";
const link = document.createElement("a");
link.href = window.location.href;
link.target = "_blank";
link.textContent = extension.getTranslation("openInTab");
linkDiv.appendChild(link);
head.appendChild(linkDiv);
}
return undefined;
}).catch(function(error){
logging.warning("Unable to identify tab:", error);
});
const groupTabs = document.createElement("div");
groupTabs.classList = "groupTabs";
document.body.appendChild(groupTabs);
const groups = document.createElement("ul");
groups.className = "groups";
groupTabs.appendChild(groups);
const table = document.createElement("table");
table.className = "settings " + (settings.displayDescriptions? "display": "hide") + "Descriptions";
settings.on("displayDescriptions", function(){
table.className = "settings " + (settings.displayDescriptions? "display": "hide") + "Descriptions";
});
const tableContainer = document.createElement("div");
tableContainer.classList = "tabContents";
groupTabs.appendChild(tableContainer);
tableContainer.appendChild(table);
const displayHidden = settings.getDefinition(settingsDisplay.displayHidden);
const searchInput = search.init();
table.appendChild(
optionsGui.createThead(
displayHidden,
searchInput
)
);
const searchOnly = searchParameters.has("searchOnly");
if (searchOnly){
document.body.classList.add("searching");
}
if (searchParameters.has("search")){
searchInput.value = searchParameters.get("search");
}
search.on(function({search, results, lastResults}){
lastResults.forEach(function(node){
node.classList.remove("found");
});
if (search || searchOnly){
document.body.classList.add("searching");
results.forEach(function(node){
node.classList.add("found");
});
}
else {
document.body.classList.remove("searching");
}
});
const {hide: hideContainer, expand: expandContainer} = settings.getContainers();
const addSection = function addSection(name){
const body = document.createElement("tbody");
body.className = "sectionBody";
if (name){
const row = document.createElement("tr");
row.className = "section";
const cell = document.createElement("td");
cell.colSpan = 3;
row.appendChild(cell);
const id = "section_" + name;
const heading = document.createElement("h2");
heading.textContent = extension.getTranslation(id);
cell.appendChild(heading);
if (expandContainer){
const collapser = document.createElement("span");
collapser.className = "collapser";
collapser.addEventListener("click", function(){
expandContainer.setExpandByName(id, !expandContainer.getExpandByName(id, true));
});
expandContainer.onExpandChange(id, function(value){
body.classList[value? "remove": "add"]("collapsed");
});
if (!expandContainer.getExpandByName(id, true)){
body.classList.add("collapsed");
}
heading.appendChild(collapser);
}
body.appendChild(row);
}
table.appendChild(body);
const rows = [];
const section = {
node: body,
addRow: function(row){
rows.push(row);
body.appendChild(row);
},
updateDisplay: function(){
const searchMode = document.body.classList.contains("searching");
let anyVisible = false;
rows.forEach(function(row){
const isHidden = row.classList.contains("hidden");
if (!isHidden){
if (searchMode){
if (!row.classList.contains("found")){
return;
}
}
if (anyVisible){
row.classList.remove("firstVisible");
}
else {
anyVisible = true;
row.classList.add("firstVisible");
}
}
});
body.classList[anyVisible? "remove": "add"]("hidden");
}
};
search.on(function(){section.updateDisplay();});
return section;
};
const addGroup = function addGroup(groupDefinition){
const sections = [];
const groupIndex = groups.childNodes.length;
const name = document.createElement("li");
name.textContent = extension.getTranslation("group_" + groupDefinition.name);
name.className = "groupName group" + groupIndex;
const group = {
select: function(){
groups.querySelectorAll(".selected").forEach(function(group){
group.classList.remove("selected");
});
table.querySelectorAll("tbody").forEach(function(section){
section.classList.remove("selectedGroup");
});
sections.forEach(function(section){
section.node.classList.add("selectedGroup");
});
name.classList.add("selected");
},
addSection: function(sectionDefinition){
const section = addSection(sectionDefinition.name);
sections.push(section);
return section;
}
};
name.addEventListener("click", group.select);
groups.appendChild(name);
return group;
};
const beforeChangeEventListeners = {};
function setupSetterForDisplay(setting){
let originalSet = setting.set;
setting.originalSet = originalSet;
if (originalSet){
const eventListeners = [];
beforeChangeEventListeners[setting.name] = eventListeners;
setting.set = function(...args){
if (eventListeners.every(function(listener){
return listener.call(setting, ...args);
})){
return originalSet.apply(this, args);
}
else {
return false;
}
};
}
}
function setupHideForDisplay(setting){
const display = setting.display;
const hideChangeListeners = [];
setting.setHide = function setHide(value){
if (hideContainer){
hideContainer.setHideByName(display.name, value);
if (setting.computeDependencies){
setting.computeDependencies();
}
}
};
setting.onHideChange = function(listener){
hideChangeListeners.push(listener);
};
setting.getHide = function getHide(){
if (hideContainer){
return hideContainer.getHideByName(display.name);
}
else {
return false;
}
};
if (hideContainer){
hideContainer.onHideChange(display.name, function(value){
if (setting.computeDependencies){
setting.computeDependencies();
}
hideChangeListeners.forEach(function(listener){
listener(value);
});
});
}
}
function setupExpandForDisplay(setting){
const display = setting.display;
const expandChangeListeners = [];
setting.setExpand = function setExpand(value){
if (expandContainer){
expandContainer.setExpandByName(display.name, value);
}
};
setting.onExpandChange = function(listener){
expandChangeListeners.push(listener);
};
setting.getExpand = function getExpand(){
if (expandContainer){
return expandContainer.getExpandByName(display.name);
}
else {
return false;
}
};
if (expandContainer){
expandContainer.onExpandChange(display.name, function(value){
expandChangeListeners.forEach(function(listener){
listener(value);
});
});
}
}
function setupComputeDependenciesForDisplay(setting, section, row){
let displayDependencies = setting.display.displayDependencies || [{}];
displayDependencies = Array.isArray(displayDependencies)?
displayDependencies:
[displayDependencies];
setting.computeDependencies = function computeDependencies(){
logging.verbose("evaluate display dependencies for", setting);
row.classList[(
(
displayHidden.get() ||
!setting.getHide()
) &&
displayDependencies.some(function(displayDependency){
return Object.keys(displayDependency).every(function(key){
return displayDependency[key].indexOf(settings[key]) !== -1;
});
})
)? "remove": "add"]("hidden");
section.updateDisplay();
};
displayDependencies.forEach(function(displayDependency){
Object.keys(displayDependency).forEach(function(name){
settings.on(name, setting.computeDependencies);
});
});
setting.computeDependencies();
displayHidden.on(setting.computeDependencies);
}
settingsDisplay.map(function(groupDefinition){
const group = addGroup(groupDefinition);
groupDefinition.sections.forEach(function(sectionDefinition){
const section = group.addSection(sectionDefinition);
sectionDefinition.settings.forEach(function(display){
let setting = settings.getDefinition(display.name);
if (!setting){
if (display.inputs){
setting = {
name: display.name,
inputs: display.inputs.map(settings.getDefinition)
};
}
else if (display.actions){
setting = {
name: display.name,
actions: display.actions.map(function(action){
return {
name: action,
action: callbacks[action]
};
}).filter(function(action){
return action.action;
})
};
}
else if (callbacks[display.name]){
setting = {
name: display.name,
action: callbacks[display.name]
};
}
}
if (setting){
setting.display = display;
setupSetterForDisplay(setting);
setupHideForDisplay(setting);
setupExpandForDisplay(setting);
const row = optionsGui.createSettingRow(setting);
settingStrings.getStrings(setting).forEach(function(string){
search.register(string, row);
});
section.addRow(row);
setupComputeDependenciesForDisplay(setting, section, row);
}
});
});
return group;
})[0].select();
const version = document.createElement("div");
version.className = "version";
fetch(browser.extension.getURL("manifest.json")).then(function(response){
return response.json();
}).then(function(manifest){
version.textContent = "Version " + manifest.version;
return manifest.version;
}).catch(function(error){
version.textContent = "Unable to get version: " + error;
});
document.body.appendChild(version);
settings.onloaded(function(){
const reCaptchaEntry = "^https://www\\.google\\.com/recaptcha/api2/(?:b?frame|anchor).*$";
const {url: urlContainer} = settings.getContainers();
settings.on("protectWindow", async function({newValue}){
if (newValue){
const urlValue = urlContainer.get();
const matching = urlValue.filter(function(entry){
return entry.url === reCaptchaEntry;
});
if (
newValue &&
(
matching.length === 0 ||
matching[0].protectWindow
)
){
const addException = await modal.confirm(
extension.getTranslation("protectWindow_askReCaptchaException"),
{
node: document.querySelector("[data-storage-name=protectWindow]"),
selector: ".settingRow .content"
}
);
if (addException){
settings.set("protectWindow", false, reCaptchaEntry);
}
}
}
});
beforeChangeEventListeners.sharePersistentRndBetweenDomains.push(function(value, ...args){
if (value){
modal.confirm(
extension.getTranslation("sharePersistentRndBetweenDomains_confirmMessage"),
{
node: document.querySelector("[data-storage-name=sharePersistentRndBetweenDomains]"),
selector: ".settingRow .content"
}
).then((reallyShare) => {
if (reallyShare){
return this.originalSet(value, ...args);
}
return false;
}).catch(function(error){
logging.warning("Unable to set sharePersistentRndBetweenDomains:", error);
});
return false;
}
return true;
});
});
searchInput.search();
}());