449 lines
14 KiB
JavaScript
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");
|
|
});
|