trisquel-icecat/icecat/extensions/gnu/jsr@javascriptrestrictor/options.js

505 lines
18 KiB
JavaScript

/** \file
* \brief Code that handles the configuration of the extension
*
* \author Copyright (C) 2019 Libor Polcak
* \author Copyright (C) 2019 Martin Timko
* \author Copyright (C) 2020 Peter Hornak
* \author Copyright (C) 2020 Pavel Pohner
* \author Copyright (C) 2022 Marek Salon
*
* \license SPDX-License-Identifier: GPL-3.0-or-later
*/
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
const MANDATORY_METADATA = ["level_id", "level_text", "level_description"];
function prepare_level_config(action_descr, params) {
var configuration_area_el = document.getElementById("configuration_area");
configuration_area_el.textContent = "";
function find_unsupported_apis(html, wrapper) {
if (is_api_undefined(wrapper)) {
return html + `<li> <code>${wrapper}</code>.</li>`;
}
return html;
}
var unsupported_apis = wrapping_groups.groups.reduce((acc, group) =>
group.wrappers.reduce(find_unsupported_apis, acc), "");
if (unsupported_apis !== "") {
unsupported_apis = `<div class="unsupported_api">${browser.i18n.getMessage("omittedAPIsHeading")} ${unsupported_apis}</div>`;
}
var fragment = document.createRange().createContextualFragment(`
<div>
<div>
<h2>${action_descr}</h2>
</div>
<p class="alert">
${browser.i18n.getMessage("newLevelsNotRecommended")}
</p>
<form>
<!-- Metadata -->
<div class="main-section">
<label for="level_text">${browser.i18n.getMessage("formlabelName")}</label>
<input id="level_text" value="${escape(params.level_text)}"></input>
<input type="hidden" id="level_id" ${params.level_id != "" ? "disabled" : ""} value="${escape(params.level_id)}"></input>
</div>
<div class="main-section">
<label for="level_description">${browser.i18n.getMessage("formlabelDescription")}</label>
<input id="level_description" value="${escape(params.level_description)}"></input>
</div>
<div id="tweaks"></div>
<button id="cancel" class="jsr-button">${browser.i18n.getMessage("ButtonCancel")}</button>
<button id="save" class="jsr-button">${browser.i18n.getMessage("ButtonSaveCustomLevel")}</button>
</form>
${unsupported_apis}
</div>`);
configuration_area_el.appendChild(fragment);
delete params["wrappers"];
let tweaks = Object.assign({}, wrapping_groups.empty_level, params);
let tweaksContainer = document.getElementById("tweaks");
let tweaksBusiness = Object.create(tweaks_gui);
tweaksBusiness.get_current_tweaks = function() {
let current = Object.assign({}, tweaks);
for (id of MANDATORY_METADATA) {
delete current[id];
}
return current;
};
tweaksBusiness.tweak_changed = function(group_id, desired_tweak) {
tweaks[group_id] = desired_tweak;
}
tweaksBusiness.create_tweaks_html(tweaksContainer);
document.getElementById("save").addEventListener("click", function(e) {
e.preventDefault();
let new_level = tweaks;
for (id of MANDATORY_METADATA) {
let elem = document.getElementById(id);
new_level[id] = elem.value;
};
if (new_level.level_id.length > 0 && new_level.level_text.length > 0 && new_level.level_description.length) {
async function updateLevels(new_level, stored_levels) {
custom_levels = stored_levels.custom_levels;
let ok = false;
if (new_level.level_id in custom_levels) {
ok = window.confirm(browser.i18n.getMessage("customLevelAlreadyExistsItWillBeOverridden", new_level.level_id));
}
else {
ok = true;
}
if (ok) {
custom_levels[new_level.level_id] = new_level;
try {
await browser.storage.sync.set({custom_levels: custom_levels});
location = "";
}
catch (err) {
alert(browser.i18n.getMessage("customLevelWereNotUpdated"));
}
}
}
browser.storage.sync.get("custom_levels").then(updateLevels.bind(null, new_level));
}
else {
alert(browser.i18n.getMessage("NewLevelMissingNameOrDescription"));
}
});
document.getElementById("cancel").addEventListener("click", function(e) {
document.location = "options.html";
});
}
function edit_level(id) {
prepare_level_config(browser.i18n.getMessage("JSSeditLevelHeading", escape(levels[id].level_text)), levels[id]);
}
function restore_level(id, level_params) {
function restoreLevel(settings) {
var custom_levels = settings.custom_levels;
custom_levels[id] = level_params;
browser.storage.sync.set({"custom_levels": custom_levels});
var existPref = document.getElementById(`li-exist-group-${escape(id)}`);
existPref.classList.remove("hidden");
var removedPref = document.getElementById(`li-removed-group-${escape(id)}`);
removedPref.classList.add("hidden");
var lielem = document.getElementById(`li-${id}`);
lielem.classList.remove("undo");
}
browser.storage.sync.get("custom_levels").then(restoreLevel);
}
function show_existing_level(levelsEl, level) {
let currentId = `level-${level}`;
var fragment = document.createRange().createContextualFragment(`<li id="li-${escape(level)}">
<button class="level" id="${escape(currentId)}" title="${escape(levels[level].level_description)}">
${escape(levels[level].level_text)}
</button><span></span>
<p class="hidden_help_text"><label class="level_button_descr" for="${escape(currentId)}">${escape(levels[level].level_description)}</label></p>
</li>`);
levelsEl.appendChild(fragment);
var lielem = document.getElementById(`li-${level}`); // Note that FF here requires unescaped ID
if (levels[level].builtin !== true) {
var existPref = document.createElement("span");
existPref.setAttribute("id", `li-exist-group-${escape(level)}`);
lielem.appendChild(existPref);
var edit = document.createElement("button");
existPref.appendChild(edit);
edit.addEventListener("click", edit_level.bind(edit, level));
edit.appendChild(document.createTextNode(browser.i18n.getMessage("ButtonEdit")));
var remove = document.createElement("button");
existPref.appendChild(remove);
remove.addEventListener("click", remove_level.bind(remove, level));
remove.appendChild(document.createTextNode(browser.i18n.getMessage("ButtonRemove")));
var removedPref = document.createElement("span");
removedPref.setAttribute("id", `li-removed-group-${escape(level)}`);
removedPref.classList.add("hidden");
lielem.appendChild(removedPref);
var restore = document.createElement("button");
removedPref.appendChild(restore);
restore.addEventListener("click", restore_level.bind(restore, level, levels[level]));
restore.appendChild(document.createTextNode(browser.i18n.getMessage("ButtonRestore")));
}
prepareHiddenHelpText(lielem.getElementsByClassName('hidden_help_text'), []);
var current = document.getElementById(escape(currentId))
current.addEventListener("click", function() {
for (let child of levelsEl.children) {
child.children[0].classList.remove("active");
}
this.classList.add("active");
setDefaultLevel(level);
});
current.addEventListener("mouseenter", function() {
if (level !== default_level.level_id) {
lev = levels[level];
update_level_details(lev.level_text + " level (currently not applied by default), details:", lev);
}
});
current.addEventListener("mouseout", update_level_details_default);
}
function remove_level(id) {
function remove_level(settings) {
var custom_levels = settings.custom_levels;
// See https://alistapart.com/article/neveruseawarning/
var existPref = document.getElementById(`li-exist-group-${escape(id)}`);
existPref.classList.add("hidden");
var removedPref = document.getElementById(`li-removed-group-${escape(id)}`);
removedPref.classList.remove("hidden");
var lielem = document.getElementById(`li-${id}`);
lielem.classList.add("undo");
delete custom_levels[id];
browser.storage.sync.set({"custom_levels": custom_levels});
}
browser.storage.sync.get("custom_levels").then(remove_level);
}
function update_level_details(heading, level) {
document.getElementById("current-level-tweaks-heading").textContent = heading;
var currentTweaksEl = document.getElementById("current-level-tweaks");
let tweaksBusiness = Object.create(tweaks_gui);
tweaksBusiness.get_current_tweaks = function() {
return getTweaksForLevel(level.level_id, {});
};
tweaksBusiness.create_tweaks_html(currentTweaksEl);
}
function update_level_details_default() {
update_level_details(default_level.level_text + " level (currently applied by default), details:", default_level);
}
function insert_levels() {
// Insert all known levels to GUI
var allLevelsElement = document.getElementById("levels-list");
for (let level in levels) {
show_existing_level(allLevelsElement, level);
}
// Select default level
document.getElementById("level-" + default_level.level_id).classList.add("active");
update_level_details_default();
}
window.addEventListener("load", async function() {
if (!levels_initialised) {
levels_updated_callbacks.push(insert_levels);
}
else {
insert_levels();
}
await Promise.all([
load_module_settings("nbs"),
load_module_settings("fpd")
])
loadWhitelist("nbs");
load_on_off_switch("nbs");
loadWhitelist("fpd");
load_on_off_switch("fpd");
});
document.getElementById("new_level").addEventListener("click", function() {
let new_level = Object.assign({}, wrapping_groups.empty_level);
let seq = Object.keys(levels).length;
let new_id;
do {
new_id = "Custom" + String(seq);
seq++;
} while (levels[new_id] !== undefined)
new_level.level_id = new_id;
prepare_level_config(browser.i18n.getMessage("JSSaddLevelHeading"), new_level)
});
document.getElementById("nbs-whitelist-show").addEventListener("click", () => show_whitelist("nbs"));
document.getElementById("nbs-whitelist-hide").addEventListener("click", () => hide_whitelist("nbs"));
document.getElementById("nbs-whitelist-add-button").addEventListener("click", () => add_to_whitelist("nbs"));
document.getElementById("nbs-whitelist-input").addEventListener('keydown', (e) => {if (e.key === 'Enter') add_to_whitelist("nbs")});
document.getElementById("nbs-whitelist-remove-button").addEventListener("click", () => remove_from_whitelist("nbs"));
document.getElementById("nbs-whitelist-select").addEventListener('keydown', (e) => {if (e.key === 'Delete') remove_from_whitelist("nbs")});
document.getElementsByClassName("slider")[0].addEventListener("click", () => {setTimeout(control_slider, 200, "nbs")});
document.getElementById("fpd-whitelist-show").addEventListener("click", () => show_whitelist("fpd"));
document.getElementById("fpd-whitelist-hide").addEventListener("click", () => hide_whitelist("fpd"));
document.getElementById("fpd-whitelist-add-button").addEventListener("click", () => add_to_whitelist("fpd"));
document.getElementById("fpd-whitelist-input").addEventListener('keydown', (e) => {if (e.key === 'Enter') add_to_whitelist("fpd")});
document.getElementById("fpd-whitelist-remove-button").addEventListener("click", () => remove_from_whitelist("fpd"));
document.getElementById("fpd-whitelist-select").addEventListener('keydown', (e) => {if (e.key === 'Delete') remove_from_whitelist("fpd")});
document.getElementsByClassName("slider")[1].addEventListener("click", () => {setTimeout(control_slider, 200, "fpd")});
async function load_module_settings(prefix) {
let settings = await browser.runtime.sendMessage({purpose: prefix + "-get-settings"});
if (settings) {
let tweaksBusiness = Object.create(tweaks_gui);
tweaksBusiness.previousValues = new Object();
tweaksBusiness.tweak_changed = function(key, val) {
let permissions = settings.def[key].params[val].permissions || [];
browser.permissions.request({permissions: permissions}).then((granted) => {
if (granted) {
tweaksBusiness.previousValues[key] = val;
}
else {
let inputElement = document.querySelector(`#${prefix}-${key}-setting input`);
inputElement.value = tweaksBusiness.previousValues[key];
inputElement.dispatchEvent(new Event("input"));
}
browser.runtime.sendMessage({purpose: prefix + "-set-settings", id: key, value: tweaksBusiness.previousValues[key]});
});
}
let targetElement = document.getElementById(prefix + "-settings");
for ([key, setting] of Object.entries(settings.def)) {
tweaksBusiness.previousValues[key] = settings.val[key];
tweaksBusiness.add_tweak_row(targetElement, {}, key, settings.val[key], setting.label, setting, true);
}
}
}
function show_whitelist(prefix) {
loadWhitelist(prefix);
var whitelist = document.getElementById(prefix + "-whitelist-container");
whitelist.classList.toggle("hidden");
document.getElementById(prefix + "-whitelist-show").classList.add("hidden");
document.getElementById(prefix + "-whitelist-hide").classList.remove("hidden");
}
function hide_whitelist(prefix) {
var whitelist = document.getElementById(prefix + "-whitelist-container");
whitelist.classList.toggle("hidden");
document.getElementById(prefix + "-whitelist-show").classList.remove("hidden");
document.getElementById(prefix + "-whitelist-hide").classList.add("hidden");
}
function add_to_whitelist(prefix)
{
//obtain input value
var to_whitelist = document.getElementById(prefix + "-whitelist-input").value;
if (to_whitelist.trim() !== '')
{
var listbox = document.getElementById(prefix + "-whitelist-select");
//Check if it's not in whitelist already
for (var i = 0; i < listbox.length; i++)
{
if (to_whitelist == listbox.options[i].text)
{
alert("Hostname is already in the whitelist.");
return;
}
}
//Insert it
listbox.options[listbox.options.length] = new Option(to_whitelist, to_whitelist);
//Update background
update_whitelist(listbox, prefix);
}
else
{
alert("Please fill in the hostname first.");
}
}
function remove_from_whitelist(prefix)
{
var listbox = document.getElementById(prefix + "-whitelist-select");
var selectedIndexes = getSelectValues(listbox);
var j = 0;
for (var i = 0; i < selectedIndexes.length; i++)
{
listbox.remove(selectedIndexes[i]-j);
j++;
}
update_whitelist(listbox, prefix);
}
function update_whitelist(listbox, prefix)
{
//Create new associative array
var whitelistedHosts = new Object();
//Obtain all whitelisted hosts from listbox
for (var i = 0; i < listbox.length; i++)
{
whitelistedHosts[listbox.options[i].text] = true;
}
if (prefix == "nbs") setStorageAndSendMessage({"nbsWhitelist":whitelistedHosts}, {message:"whitelist updated"});
if (prefix == "fpd") setStorageAndSendMessage({"fpdWhitelist":whitelistedHosts}, {purpose:"update-fpd-whitelist"});
}
//Overwrite the whitelist in storage and send message to background
function setStorageAndSendMessage(setter, message)
{
browser.storage.sync.set(setter);
browser.runtime.sendMessage(message);
}
//Auxilary function for obtaining selected values from listbox
function getSelectValues(select)
{
var result = [];
var options = select && select.options;
var opt;
for (var i=0, iLen=options.length; i<iLen; i++) {
opt = options[i];
if (opt.selected) {
result.push(i);
}
}
return result;
}
//Function called on window load, obtains whitelist from storage
//Displays it in listbox
function loadWhitelist(prefix)
{
var listbox = document.getElementById(prefix + "-whitelist-select");
listbox.options.length = 0;
var whitelistName = prefix + "Whitelist";
//Get the whitelist
browser.storage.sync.get([whitelistName]).then(function(result)
{
if (result[whitelistName] != undefined)
{
//Create list box options for each item
var it = 0;
Object.keys(result[whitelistName]).forEach(function(key, index) {
listbox.options[it++] = new Option(key, key);
}, result[whitelistName]);
}
});
}
//Function called on window load, obtains whether is the protection on or off
function load_on_off_switch(prefix)
{
var checkbox = document.getElementById(prefix + "-switch");
var flagName;
if (prefix == "nbs") flagName = "requestShieldOn";
if (prefix == "fpd") flagName = "fpDetectionOn";
//Obtain the information from storage
browser.storage.sync.get([flagName]).then(function(result)
{
//Check the box
if (result[flagName] || result[flagName] == undefined)
{
checkbox.checked = true;
toggleSettings(prefix, true);
}
else
{
checkbox.checked = false;
toggleSettings(prefix, false);
}
});
}
//OnClick event handler for On/Off slider
function control_slider(prefix)
{
var checkbox = document.getElementById(prefix + "-switch");
//Send appropriate message and store state into storage
if (checkbox.checked) //Turn ON
{
if (prefix == "nbs") setStorageAndSendMessage({"requestShieldOn": true}, {message:"turn request shield on"});
if (prefix == "fpd") setStorageAndSendMessage({"fpDetectionOn": true}, {purpose:"fpd-state-change"});
toggleSettings(prefix, true);
}
else
{
if (prefix == "nbs") setStorageAndSendMessage({"requestShieldOn": false}, {message:"turn request shield off"});
if (prefix == "fpd") setStorageAndSendMessage({"fpDetectionOn": false}, {purpose:"fpd-state-change"});
toggleSettings(prefix, false);
}
}
function toggleSettings(prefix, enable) {
tweaksArr = document.querySelectorAll(`#${prefix}-settings input.tlev`);
tweaksArr.forEach((node) => {
node.disabled = !enable;
})
}
function prepareHiddenHelpText(originally_hidden_elements, originally_visible_elements = []) {
Array.prototype.forEach.call(originally_hidden_elements, it => it.classList.add("hidden_descr"));
let all_elements = Array.from(originally_hidden_elements).concat(Array.from(originally_visible_elements));
var ctrl = document.createElement("button");
ctrl.innerText = "?";
ctrl.classList.add("help");
ctrl.addEventListener("click", function(ev) {
Array.prototype.forEach.call(all_elements, it => it.classList.toggle("hidden_descr"));
ev.preventDefault();
});
originally_hidden_elements[0].previousElementSibling.insertAdjacentElement("beforeend", ctrl);
}
window.addEventListener("DOMContentLoaded", function() {
function prepareHelpText(prefix) {
prepareHiddenHelpText(document.getElementsByClassName(prefix + "_description"));
}
prepareHelpText("jss");
prepareHelpText("nbs");
prepareHelpText("fpd");
});