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

449 lines
14 KiB
JavaScript

/** \file
* \brief JS code for pop up
*
* \author Copyright (C) 2019 Martin Timko
* \author Copyright (C) 2019 Libor Polcak
* \author Copyright (C) 2020 Pavel Pohner
* \author Copyright (C) 2021 Marek Salon
* \author Copyright (C) 2022 Giorgio Maone
*
* \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/>.
//
var site; // "https://www.example.com" from current tab will become "example.com"
var pageConfiguration = {};
var hits;
var current_level;
var default_lev_button;
var fpd_active = false;
// Provide cusom handlers for Tweaks GUI
let popup_tweaks = Object.create(tweaks_gui);
popup_tweaks.get_current_tweaks = function() {
return getTweaksForLevel(current_level.level_id, current_level.tweaks);
};
popup_tweaks.tweak_changed = function(group_id, desired_tweak) {
if (!current_level.tweaks) {
current_level.tweaks = {};
}
current_level.tweaks[group_id] = desired_tweak;
domains[site] = current_level;
saveDomainLevels();
enableRefreshIfNeeded();
};
popup_tweaks.assign_custom_params = function(group) {
group.groupHits = hits[group.name] || 0;
};
popup_tweaks.customize_tweak_row = function (tweakRow, group) {
// Don't show hits for WASM optimization group tweak
if (group.name === "wasm") {
tweakRow.querySelector(".hits").textContent = "-";
return;
}
let groupHits = group.groupHits;
if (groupHits >= 999) {
groupHits = "1000 or more";
}
else if (!fpd_active) {
let main = current_level[group.name];
let tweaks = current_level.tweaks;
if (!(tweaks && (tweaks[group.name] > 0))) {
if ((tweaks && tweaks[group.name] === 0) || (!(main > 0))) {
groupHits = String(groupHits) + " (unreliable)";
}
}
}
tweakRow.querySelector(".hits").textContent = groupHits;
};
popup_tweaks.cmp_groups = function (firstObj, secondObj) {
let firstGr = firstObj.group;
let secondGr = secondObj.group;
return secondGr.groupHits - firstGr.groupHits;
};
/**
* Enable the refresh page option.
*/
function showRefreshPageOption(toggle = true) {
let button = document.getElementById('refresh-page');
button.addEventListener('click', function (e) {
browser.tabs.reload();
window.close();
});
(showRefreshPageOption = function(toggle = true) {
button.disabled = !toggle;
})(toggle);
}
/**
* Visaully highlights the active level.
*/
function changeActiveLevel(activeEl) {
Array.prototype.forEach.call(document.getElementsByClassName("level_control"),
(el) => el.classList.remove("active"));
activeEl.classList.add("active");
showRefreshPageOption();
}
/**
* Control the state of the Refresh button
*/
function enableRefreshIfNeeded() {
let pageLevel = pageConfiguration.currentLevel;
let level4comp = ({level_id, tweaks}) => JSON.stringify({level_id, tweaks});
let needsRefresh = !pageLevel ||
level4comp(pageLevel) !== level4comp(current_level);
if (needsRefresh) {
showRefreshPageOption();
}
}
/**
* Save level settings for current page, show correct button
*/
function modify_level(level, levelButton, forceUpdateLevels = false) {
if (level) {
current_level = level;
saveDomainLevels();
if (levelButton) {
changeActiveLevel(levelButton);
}
update_level_info();
if (forceUpdateLevels) {
function refresh() {
current_level = getCurrentLevelJSON("https://" + site);
update_tweaks();
}
browser.storage.sync.get(null).then(updateLevels).then(refresh);
}
else {
update_tweaks();
}
}
}
/**
* Disable JSS for this page.
*/
async function disable_jss(orig_level) {
//document.getElementById("jss-switch").checked = false;
current_level = Object.assign(orig_level ? {restore: orig_level.level_id} : {}, level_0);
if (orig_level && orig_level.tweaks) {
current_level.restore_tweaks = orig_level.tweaks;
}
document.getElementById("js-settings").classList.add("hidden");
document.getElementById("js-toggle-btn").classList.add("hidden");
domains[site] = current_level;
modify_level(current_level);
document.getElementById("jss-switch").onchange = () => {
enable_jss(orig_level);
let widget = document.getElementById("js-toggle");
widget.checked = true;
widget.parentElement.classList.toggle("toggled", true);
};
}
async function enable_jss(level) {
let isdefault = !domains[site];
if (!level) {
level = Object.assign({}, default_level);
if (level.level_id === level_0.level_id) {
level = Object.assign({}, level_2);
isdefault = false;
}
else {
isdefault = true;
}
}
if (level.level_id === level_0.level_id) {
document.getElementById("jss-switch").checked = false;
let restore = level.restore;
if (restore) {
disable_jss(Object.assign(level.restore_tweaks ? {tweaks: level.restore_tweaks} : {}, levels[restore]));
}
else {
disable_jss();
}
return;
}
if (isdefault) {
delete domains[site];
}
else {
domains[site] = level;
}
modify_level(level, isdefault ? default_lev_button : levels[level.level_id].button);
document.getElementById("js-settings").classList.remove("hidden");
document.getElementById("js-toggle-btn").classList.remove("hidden");
document.getElementById("jss-switch").onchange = () => {
isdefault ? disable_jss() : disable_jss(current_level);
};
}
/**
* Load levels and add level setting buttons
*/
function add_level_buttons() {
var selectEl = document.getElementById("level-select");
function addButton(level) {
let b = document.createElement("button");
b.id = `select-${level.level_id}`;
b.className = "level level_control";
b.textContent = level.level_text;
b.title = level.level_description;
level.button = b;
return selectEl.appendChild(b);
}
// Add default level button
default_lev_button = addButton({level_id: "DEFAULT", level_description: browser.i18n.getMessage("popupDefaultLevelHelpText"), level_text: browser.i18n.getMessage("defaultLevelSelection", default_level.level_text)});
default_lev_button.addEventListener("click", ev => {
delete domains[site];
modify_level(default_level, ev.target, true); // We need to force update config to display built-in tweaks
});
// Load built-in and custom levels
for (let level_id in levels) {
let level = levels[level_id];
addButton(level).addEventListener("click", function (ev) {
domains[site] = {
level_id: level.level_id,
};
modify_level(level, ev.target);
});
}
let currentEl = document.getElementById(`select-${current_level.is_default ? "DEFAULT" : current_level.level_id}`);
if (currentEl !== null) {
currentEl.classList.add("active");
}
}
function update_level_info() {
const LIMIT = 300;
document.getElementById("level-text").textContent = current_level.level_text;
let descriptionEl = document.getElementById("level-description");
let moreEl = document.getElementById("current_site_level_settings").querySelector(".more");
let lessEl = document.getElementById("current_site_level_settings").querySelector(".less");
let shortDescr = create_short_text(current_level.level_description, LIMIT);
descriptionEl.textContent = ` - ${shortDescr}`;
if (current_level.level_description != shortDescr) {
lessEl.classList.add("hidden_descr");
moreEl.classList.remove("hidden_descr");
moreEl.onclick = function() {
descriptionEl.textContent = " - " + current_level.level_description;
moreEl.classList.add("hidden_descr");
lessEl.classList.remove("hidden_descr");
return false;
}
lessEl.onclick = function() {
descriptionEl.textContent = ` - ${shortDescr}`;
lessEl.classList.add("hidden_descr");
moreEl.classList.remove("hidden_descr");
return false;
}
}
else {
lessEl.classList.add("hidden_descr");
moreEl.classList.add("hidden_descr");
}
}
function update_tweaks() {
let tweaksContainer = document.getElementById("tweaks");
document.body.classList.remove("tweaking");
let tweakBtn = document.getElementById("btn-tweak");
tweakBtn.disabled = true;
if (current_level.tweaks && Object.keys(current_level.tweaks).length) {
popup_tweaks.create_tweaks_html(tweaksContainer);
tweakBtn.disabled = true;
}
else if (current_level.level_id !== "0") {
tweakBtn.disabled = false;
tweakBtn.onclick = function() {
popup_tweaks.create_tweaks_html(tweaksContainer);
tweakBtn.disabled = true;
};
}
}
/**
* Fill the content of the JS Shield settings
*/
function fill_jsshield(msg) {
current_level = msg;
enable_jss(msg);
enableRefreshIfNeeded();
fpdGetSeverity();
add_level_buttons();
update_level_info();
update_tweaks();
}
async function init_jsshield() {
// get the site of current tab
site = await getCurrentSite();
// abort per-site options if it can't be accessed
if (!site) return;
document.getElementById('site-settings').classList.add("enabled");
document.getElementById('current-site').textContent = site;
// fill the popup
var port_to_background = browser.runtime.connect({name:"port_from_popup"});
current_level = { level_id: "?" };
port_to_background.onMessage.addListener(fill_jsshield);
}
// Open options in a new tab when clicking on the icon
document.getElementById("global-settings").addEventListener('click', function(e) {
browser.runtime.openOptionsPage();
window.close();
});
{
let widget = document.getElementById('js-toggle');
let key = `controls-${widget.id}-checked`;
widget.checked = localStorage.getItem(key) === 'true';
(widget.onchange = () => {
let {checked} = widget;
widget.parentElement.classList.toggle("toggled", checked);
localStorage.setItem(key, checked);
})();
}
document.getElementById("nbs-switch").addEventListener("change", () => {setTimeout(control_whitelist, 200, "nbs")});
document.getElementById("fpd-switch").addEventListener("change", () => {setTimeout(control_whitelist, 200, "fpd")});
async function getCurrentSite() {
if (typeof site !== "undefined") return site;
try {
let [tab] = await browser.tabs.query({currentWindow: true, active: true});
// Check whether content scripts are allowed on the current tab:
// if an exception is thrown, showing per-site settings is pointless,
// because we couldn't operate here anyway
if ("executeScript" in browser.tabs) {
[pageConfiguration = {}] = await browser.tabs.executeScript({
code: `typeof pageConfiguration === "object" && pageConfiguration || {};`
});
} else {
pageConfiguration = (await browser.scripting.executeScript({
injectImmediately: true,
func: () => typeof pageConfiguration === "object" && pageConfiguration || {},
target: {tabId: tab.id},
}))[0].result;
}
hits = await browser.runtime.sendMessage({purpose: 'fpd-fetch-hits', tabId: tab.id});
// Obtain and normalize hostname
return site = getEffectiveDomain(tab.url);
} catch (e) {
if (/^Error: (?:Missing host permissions|Could not establish connection)/.test(e.toString())) {
console.warn("Recoverable timing error during popup startup, retrying in 300ms", e);
await async_sleep(200);
return await getCurrentSite();
}
console.error(e);
return site = null;
}
}
document.getElementById("severity_value").addEventListener("click", async function () {
let [tab] = await browser.tabs.query({currentWindow: true, active: true});
browser.runtime.sendMessage({purpose: "fpd-create-report", tabId: tab.id});
});
/// Get fingerprinting severity from FPD and show it in a popup
async function fpdGetSeverity() {
let [tab] = await browser.tabs.query({currentWindow: true, active: true});
let response = await browser.runtime.sendMessage({purpose: "fpd-fetch-severity", tabId: tab.id});
if (response) {
let element = document.getElementById("severity_value");
if (response[1]) {
element.innerText = response[1];
document.getElementById("severity_container").classList.remove("hidden");
}
if (response[2]) {
element.style.backgroundColor = response[2];
}
}
setTimeout(fpdGetSeverity, 2000);
}
/// Load switch state from storage for current site
async function load_on_off_switch(prefix)
{
var flagName;
if (prefix == "nbs") flagName = "requestShieldOn";
if (prefix == "fpd") flagName = "fpDetectionOn";
let result = await browser.storage.sync.get([flagName]);
let container = document.getElementById(prefix + "_whitelist");
if (result[flagName] === false)
{
container.classList.add("off");
}
else
{
container.classList.remove("off");
//Ask background whether is this site whitelisted or not
if (prefix == "nbs")
{
let response = await browser.runtime.sendMessage({message: "is current site whitelisted?", site});
document.getElementById(prefix + "-switch").checked = response !== "current site is whitelisted";
}
if (prefix == "fpd")
{
let response = await browser.runtime.sendMessage({purpose: "fpd-whitelist-check", url: site});
document.getElementById(prefix + "-switch").checked = !response;
fpd_active = !response;
}
}
}
/// Event handler for On/off switch
async function control_whitelist(prefix)
{
let site = await getCurrentSite();
var message;
if (document.getElementById(prefix + "-switch").checked) {
if (prefix == "nbs") message = "remove site from whitelist";
if (prefix == "fpd") message = "remove-fpd-whitelist";
}
else {
if (prefix == "nbs") message = "add site to whitelist";
if (prefix == "fpd") message = "add-fpd-whitelist";
}
if (prefix == "nbs") browser.runtime.sendMessage({message, site});
if (prefix == "fpd") browser.runtime.sendMessage({purpose: message, url: site});
showRefreshPageOption();
}
addEventListener("DOMContentLoaded", async () => {
await init_jsshield();
if (!site) {
return;
}
load_on_off_switch("nbs");
load_on_off_switch("fpd");
});