From 6247f163d3ca89d5570450ac7ac8fd18f73bb74b Mon Sep 17 00:00:00 2001 From: Wojtek Kosior Date: Thu, 2 Sep 2021 18:35:49 +0200 Subject: enable toggling of global script blocking policy\n\nThis commit also introduces `light_storage' module which is later going to replace the storage code we use right now.\nAlso included is a hack to properly display scrollbars under Mozilla (needs testing on newer Mozilla browsers). --- background/main.js | 6 +- background/page_actions_server.js | 11 ++-- build.sh | 11 ++++ common/observable.js | 28 ++++----- common/storage_light.js | 129 ++++++++++++++++++++++++++++++++++++++ common/storage_raw.js | 11 +++- content/main.js | 2 +- content/page_actions.js | 2 +- html/MOZILLA_scrollbar_fix.css | 46 ++++++++++++++ html/base.css | 8 +++ html/default_blocking_policy.html | 18 ++++++ html/default_blocking_policy.js | 47 ++++++++++++++ html/display-panel.html | 24 ++++--- html/display-panel.js | 5 +- html/import_frame.html | 7 +++ html/options.html | 1 + html/options_main.js | 3 + 17 files changed, 322 insertions(+), 37 deletions(-) create mode 100644 common/storage_light.js create mode 100644 html/MOZILLA_scrollbar_fix.css create mode 100644 html/default_blocking_policy.html create mode 100644 html/default_blocking_policy.js diff --git a/background/main.js b/background/main.js index 5d6e680..b1c252a 100644 --- a/background/main.js +++ b/background/main.js @@ -9,6 +9,7 @@ * IMPORTS_START * IMPORT TYPE_PREFIX * IMPORT get_storage + * IMPORT light_storage * IMPORT start_storage_server * IMPORT start_page_actions_server * IMPORT browser @@ -50,6 +51,7 @@ browser.runtime.onInstalled.addListener(init_ext); let storage; +let policy_observable = {}; function on_headers_received(details) { @@ -58,7 +60,7 @@ function on_headers_received(details) return; const [pattern, settings] = query_best(storage, details.url); - const allow = !!(settings && settings.allow); + const allow = !!(settings ? settings.allow : policy_observable.value); const nonce = gen_nonce(); const policy = {allow, url, nonce}; @@ -114,6 +116,8 @@ async function start_webRequest_operations() {urls: [""], types: all_types}, extra_opts.concat("requestHeaders") ); + + policy_observable = await light_storage.observe_var("default_allow"); } start_webRequest_operations(); diff --git a/background/page_actions_server.js b/background/page_actions_server.js index 58a0073..b0db5f5 100644 --- a/background/page_actions_server.js +++ b/background/page_actions_server.js @@ -8,6 +8,7 @@ /* * IMPORTS_START * IMPORT get_storage + * IMPORT light_storage * IMPORT TYPE_PREFIX * IMPORT CONNECTION_TYPE * IMPORT browser @@ -20,17 +21,17 @@ var storage; var handler; +let policy_observable; function send_actions(url, port) { - const [pattern, settings] = query_best(storage, url); + let [pattern, settings] = query_best(storage, url); + if (!settings) + settings = {allow: policy_observable && policy_observable.value}; const repos = storage.get_all(TYPE_PREFIX.REPO); port.postMessage(["settings", [pattern, settings, repos]]); - if (settings === undefined) - return; - let components = settings.components; let processed_bags = new Set(); @@ -127,6 +128,8 @@ async function start_page_actions_server() storage = await get_storage(); listen_for_connection(CONNECTION_TYPE.PAGE_ACTIONS, new_connection); + + policy_observable = await light_storage.observe_var("default_allow"); } /* diff --git a/build.sh b/build.sh index 31f3dec..0659ed1 100755 --- a/build.sh +++ b/build.sh @@ -291,6 +291,17 @@ EOF cp html/*.css $BUILDDIR/html mkdir $BUILDDIR/icons cp icons/*.png $BUILDDIR/icons + + if [ "$BROWSER" = "chromium" ]; then + for MOZILLA_FILE in $(find $BUILDDIR -name "MOZILLA_*"); do + echo > "$MOZILLA_FILE" + done + fi + if [ "$BROWSER" = "mozilla" ]; then + for CHROMIUM_FILE in $(find $BUILDDIR -name "CHROMIUM_*"); do + echo > "$CHROMIUM_FILE" + done + fi } main "$@" diff --git a/common/observable.js b/common/observable.js index 1fb0b0a..02f1c1b 100644 --- a/common/observable.js +++ b/common/observable.js @@ -6,28 +6,22 @@ * Redistribution terms are gathered in the `copyright' file. */ -function make() -{ - return new Set(); -} +const make = (value=undefined) => ({value, listeners: new Set()}); +const subscribe = (observable, cb) => observable.listeners.add(cb); +const unsubscribe = (observable, cb) => observable.listeners.delete(cb); -function subscribe(observable, cb) -{ - observable.add(cb); -} - -function unsubscribe(observable, cb) -{ - observable.delete(cb); -} +const silent_set = (observable, value) => observable.value = value; +const broadcast = (observable, ...values) => + observable.listeners.forEach(cb => cb(...values)); -function broadcast(observable, event) +function set(observable, value) { - for (const callback of observable) - callback(event); + const old_value = observable.value; + silent_set(observable, value); + broadcast(observable, value, old_value); } -const observables = {make, subscribe, unsubscribe, broadcast}; +const observables = {make, subscribe, unsubscribe, broadcast, silent_set, set}; /* * EXPORTS_START diff --git a/common/storage_light.js b/common/storage_light.js new file mode 100644 index 0000000..067bf0c --- /dev/null +++ b/common/storage_light.js @@ -0,0 +1,129 @@ +/** + * part of Hachette + * Storage manager, lighter than the previous one. + * + * Copyright (C) 2021 Wojtek Kosior + * Redistribution terms are gathered in the `copyright' file. + */ + +/* + * IMPORTS_START + * IMPORT TYPE_PREFIX + * IMPORT raw_storage + * IMPORT is_mozilla + * IMPORT observables + */ + +const reg_spec = new Set(["\\", "[", "]", "(", ")", "{", "}", ".", "*", "+"]); +const escape_reg_special = c => reg_spec.has(c) ? "\\" + c : c; + +function make_regex(name) +{ + return new RegExp(`^${name.split("").map(escape_reg_special).join("")}\$`); +} + +const listeners_by_callback = new Map(); + +function listen(callback, prefix, name) +{ + let by_prefix = listeners_by_callback.get(callback); + if (!by_prefix) { + by_prefix = new Map(); + listeners_by_callback.set(callback, by_prefix); + } + + let by_name = by_prefix.get(prefix); + if (!by_name) { + by_name = new Map(); + by_prefix.set(prefix, by_name); + } + + let name_reg = by_name.get(name); + if (!name_reg) { + name_reg = name.test ? name : make_regex(name); + by_name.set(name, name_reg); + } +} + +function no_listen(callback, prefix, name) +{ + const by_prefix = listeners_by_callback.get(callback); + if (!by_prefix) + return; + + const by_name = by_prefix.get(prefix); + if (!by_name) + return; + + const name_reg = by_name.get(name); + if (!name_reg) + return; + + by_name.delete(name); + + if (by_name.size === 0) + by_prefix.delete(prefix); + + if (by_prefix.size === 0) + listeners_by_callback.delete(callback); +} + +function storage_change_callback(changes, area) +{ + if (is_mozilla && area !== "local") + {console.log("area", area);return;} + + for (const item of Object.keys(changes)) { + for (const [callback, by_prefix] of listeners_by_callback.entries()) { + const by_name = by_prefix.get(item[0]); + if (!by_name) + continue; + + for (const reg of by_name.values()) { + if (!reg.test(item.substring(1))) + continue; + + try { + callback(item, changes[item]); + } catch(e) { + console.error(e); + } + } + } + } +} + +raw_storage.listen(storage_change_callback); + + +const created_observables = new Map(); + +async function observe(prefix, name) +{ + const observable = observables.make(); + const callback = (it, ch) => observables.set(observable, ch.newValue); + listen(callback, prefix, name); + created_observables.set(observable, [callback, prefix, name]); + observables.silent_set(observable, await raw_storage.get(prefix + name)); + + return observable; +} + +const observe_var = name => observe(TYPE_PREFIX.VAR, name); + +function no_observe(observable) +{ + no_listen(...created_observables.get(observable) || []); + created_observables.delete(observable); +} + +const light_storage = {}; +Object.assign(light_storage, raw_storage); +Object.assign(light_storage, + {listen, no_listen, observe, observe_var, no_observe}); + +/* + * EXPORTS_START + * EXPORT light_storage + * EXPORTS_END + */ diff --git a/common/storage_raw.js b/common/storage_raw.js index 9ce3980..4c02ee4 100644 --- a/common/storage_raw.js +++ b/common/storage_raw.js @@ -26,8 +26,9 @@ async function get(key) async function set(key_or_object, value) { - return browser.storage.local.set(typeof key_or_object === "object" ? - key_or_object : {[key]: value}); + const arg = typeof key_or_object === "object" ? + key_or_object : {[key_or_object]: value}; + return browser.storage.local.set(arg); } async function set_var(name, value) @@ -40,7 +41,11 @@ async function get_var(name) return get(TYPE_PREFIX.VAR + name); } -const raw_storage = {get, set, get_var, set_var}; +const on_changed = browser.storage.onChanged || browser.storage.local.onChanged; +const listen = cb => on_changed.addListener(cb); +const no_listen = cb => on_changed.removeListener(cb); + +const raw_storage = {get, set, get_var, set_var, listen, no_listen}; /* * EXPORTS_START diff --git a/content/main.js b/content/main.js index 6c97350..17b6b98 100644 --- a/content/main.js +++ b/content/main.js @@ -123,7 +123,7 @@ if (!is_privileged_url(document.URL)) { } if (!policy) { - console.warn("Using default policy!"); + console.warn("Using fallback policy!"); policy = {allow: false, nonce: gen_nonce()}; } diff --git a/content/page_actions.js b/content/page_actions.js index 6a6b3a0..bf76790 100644 --- a/content/page_actions.js +++ b/content/page_actions.js @@ -36,7 +36,7 @@ function handle_message(message) } if (action === "settings") { report_settings(data); - policy_received_callback({url, allow: !!data[1] && data[1].allow}); + policy_received_callback({url, allow: data[1].allow}); } } diff --git a/html/MOZILLA_scrollbar_fix.css b/html/MOZILLA_scrollbar_fix.css new file mode 100644 index 0000000..5feb7c3 --- /dev/null +++ b/html/MOZILLA_scrollbar_fix.css @@ -0,0 +1,46 @@ +/** + * Hachette + * Hacky fix for vertical scrollbar width being included in child's width. + * + * Copyright (C) 2021 Wojtek Kosior + * Redistribution terms are gathered in the `copyright' file. + */ + +/* + * Under Mozilla browsers to avoid vertical scrollbar forcing horizontal + * scrollbar to appear in an element we add the `firefox_scrollbars_hacky_fix' + * class to an element for which width has to be reserved. + * + * This is a bit hacky and relies on some assumed width of Firefox scrollbar, I + * know. And must be excluded from Chromium builds. + * + * I came up with this hack when working on popup. Before that I had the + * scrollbar issue with tables in the options page and gave up there and made + * the scrollbal always visible. Now we could try applying this "fix" there, + * too! + */ + +.firefox_scrollbars_hacky_fix { + font-size: 0; +} + +.firefox_scrollbars_hacky_fix>div { + display: inline-block; + width: -moz-available; +} + +.firefox_scrollbars_hacky_fix>*>* { + font-size: initial; +} + +.firefox_scrollbars_hacky_fix::after { + content: ""; + display: inline-block; + visibility: hidden; + font-size: initial; + width: 14px; +} + +.firefox_scrollbars_hacky_fix.has_inline_content::after { + width: calc(14px - 0.3em); +} diff --git a/html/base.css b/html/base.css index 94b3f31..df52f7c 100644 --- a/html/base.css +++ b/html/base.css @@ -100,6 +100,14 @@ textarea: { background: linear-gradient(#555, transparent); } +.has_bottom_thin_line { + border-bottom: dashed #4CAF50 1px; +} + +.has_upper_thin_line { + border-top: dashed #4CAF50 1px; +} + .nowrap { white-space: nowrap; } diff --git a/html/default_blocking_policy.html b/html/default_blocking_policy.html new file mode 100644 index 0000000..50c19ca --- /dev/null +++ b/html/default_blocking_policy.html @@ -0,0 +1,18 @@ + + + + Default policy for unmatched pages is to + + their own scripts. + + diff --git a/html/default_blocking_policy.js b/html/default_blocking_policy.js new file mode 100644 index 0000000..2f49bac --- /dev/null +++ b/html/default_blocking_policy.js @@ -0,0 +1,47 @@ +/** + * part of Hachette + * Default policy dialog logic. + * + * Copyright (C) 2021 Wojtek Kosior + * Redistribution terms are gathered in the `copyright' file. + */ + +/* + * IMPORTS_START + * IMPORT by_id + * IMPORT light_storage + * IMPORT observables + * IMPORTS_END + */ + +/* + * Used with `default_blocking_policy.html' to allow user to choose whether to + * block scripts globally or not. + */ + +const blocking_policy_span = by_id("blocking_policy_span"); +const current_policy_span = by_id("current_policy_span"); +const toggle_policy_but = by_id("toggle_policy_but"); + +let policy_observable; + +const update_policy = + allowed => current_policy_span.textContent = allowed ? "allow" : "block"; +const toggle_policy = + () => light_storage.set_var("default_allow", !policy_observable.value); + +async function init_default_policy_dialog() +{ + policy_observable = await light_storage.observe_var("default_allow"); + update_policy(policy_observable.value); + observables.subscribe(policy_observable, update_policy); + + toggle_policy_but.addEventListener("click", toggle_policy); + blocking_policy_span.classList.remove("hide"); +} + +/* + * EXPORTS_START + * EXPORT init_default_policy_dialog + * EXPORTS_END + */ diff --git a/html/display-panel.html b/html/display-panel.html index 0806f26..a8c52b6 100644 --- a/html/display-panel.html +++ b/html/display-panel.html @@ -11,10 +11,11 @@ +