diff options
author | Wojtek Kosior <koszko@koszko.org> | 2021-09-02 18:35:49 +0200 |
---|---|---|
committer | Wojtek Kosior <koszko@koszko.org> | 2021-09-02 18:35:49 +0200 |
commit | 6247f163d3ca89d5570450ac7ac8fd18f73bb74b (patch) | |
tree | e3d4817ae475e1f3553d3a50a77792fc5c3c25a0 | |
parent | 4b59dced912fb9b50ff041c67f0f72cbbad56b6c (diff) | |
download | browser-extension-6247f163d3ca89d5570450ac7ac8fd18f73bb74b.tar.gz browser-extension-6247f163d3ca89d5570450ac7ac8fd18f73bb74b.zip |
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).
-rw-r--r-- | background/main.js | 6 | ||||
-rw-r--r-- | background/page_actions_server.js | 11 | ||||
-rwxr-xr-x | build.sh | 11 | ||||
-rw-r--r-- | common/observable.js | 28 | ||||
-rw-r--r-- | common/storage_light.js | 129 | ||||
-rw-r--r-- | common/storage_raw.js | 11 | ||||
-rw-r--r-- | content/main.js | 2 | ||||
-rw-r--r-- | content/page_actions.js | 2 | ||||
-rw-r--r-- | html/MOZILLA_scrollbar_fix.css | 46 | ||||
-rw-r--r-- | html/base.css | 8 | ||||
-rw-r--r-- | html/default_blocking_policy.html | 18 | ||||
-rw-r--r-- | html/default_blocking_policy.js | 47 | ||||
-rw-r--r-- | html/display-panel.html | 24 | ||||
-rw-r--r-- | html/display-panel.js | 5 | ||||
-rw-r--r-- | html/import_frame.html | 7 | ||||
-rw-r--r-- | html/options.html | 1 | ||||
-rw-r--r-- | html/options_main.js | 3 |
17 files changed, 322 insertions, 37 deletions
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: ["<all_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"); } /* @@ -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 @@ +<!-- + Copyright (C) 2021 Wojtek Kosior + Redistribution terms are gathered in the `copyright' file. + + This is not a standalone page. This file is meant to be imported into other + HTML code. + --> +<style> + #blocking_policy_div { + line-height: 2em; + } +</style> +<span id="blocking_policy_span"> + Default policy for unmatched pages is to + <span id="current_policy_span" class="bold"></span> + their own scripts. + <button id="toggle_policy_but">Toggle policy</button> +</span> 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 @@ <link type="text/css" rel="stylesheet" href="base.css" /> <link type="text/css" rel="stylesheet" href="back_button.css" /> <link type="text/css" rel="stylesheet" href="table.css" /> + <link type="text/css" rel="stylesheet" href="MOZILLA_scrollbar_fix.css" /> <style> body { width: max-content; - width: -moz-max-content; + width: -moz-fit-content; } .top>h2 { @@ -114,8 +115,6 @@ pre { font-family: monospace; background-color: white; - border-top: dashed #4CAF50 1px; - border-bottom: dashed #4CAF50 1px; padding: 1px 5px; } @@ -133,8 +132,11 @@ padding-right: 5px; } + .padding_top { + padding-top: 5px; + } + .header { - border-bottom: dashed #4CAF50 1px; padding-bottom: 0.3em; margin-bottom: 0.5em; text-align: center; @@ -146,7 +148,6 @@ } .footer { - border-top: dashed #4CAF50 1px; padding-top: 0.3em; margin-top: 0.5em; text-align: center; @@ -199,7 +200,7 @@ <label data-template="lbl"> <h3><div class="unroll_triangle"></div> script</h3> </label> - <pre data-template="script_contents"></pre> + <pre class="has_bottom_thin_line has_upper_thin_line" data-template="script_contents"></pre> </div> </div> @@ -242,7 +243,7 @@ Patterns higher are more specific and override the ones below. </aside> </div> - <div class="table_wrapper"> + <div class="table_wrapper firefox_scrollbars_hacky_fix"> <div> <table> <tbody id="possible_patterns"> @@ -250,6 +251,11 @@ </table> </div> </div> + <div class="padding_inline padding_top has_upper_thin_line firefox_scrollbars_hacky_fix has_inline_content"> + <span class="nowrap"> + <IMPORT html/default_blocking_policy.html /> + </span> + </div> </div> <input id="show_queried_view_radio" type="radio" class="show_next" name="current_view"></input> @@ -265,7 +271,7 @@ <h3 id="privileged_notice" class="middle hide">Privileged page</h3> <div id="page_state" class="hide"> - <div class="header padding_inline"> + <div class="header padding_inline has_bottom_thin_line"> <label for="show_patterns_view_radio" class="button"> Edit settings for this page </label> @@ -317,7 +323,7 @@ </div> </div> - <div class="footer padding_inline"> + <div class="footer padding_inline has_upper_thin_line"> <button id="settings_but" type="button"> Open Hachette settings </button> diff --git a/html/display-panel.js b/html/display-panel.js index bd210c1..ed96c07 100644 --- a/html/display-panel.js +++ b/html/display-panel.js @@ -14,6 +14,7 @@ *** temporarily, before all storage access gets reworked. * IMPORT get_remote_storage * IMPORT get_import_frame + * IMPORT init_default_policy_dialog * IMPORT query_all * IMPORT CONNECTION_TYPE * IMPORT is_privileged_url @@ -243,7 +244,6 @@ function handle_activity_report(message) if (type === "settings") { let [pattern, settings] = data; - settings = settings || {}; blocked_span.textContent = settings.allow ? "no" : "yes"; if (pattern) { @@ -254,6 +254,7 @@ function handle_activity_report(message) view_pattern_but.addEventListener("click", settings_opener); } else { pattern_span.textContent = "none"; + blocked_span.textContent = blocked_span.textContent + " (default)"; } const components = settings.components; @@ -549,6 +550,8 @@ by_id("settings_but") async function main() { + init_default_policy_dialog(); + storage = await get_remote_storage(); import_frame = await get_import_frame(); import_frame.onclose = () => show_queried_view_radio.checked = true; diff --git a/html/import_frame.html b/html/import_frame.html index e6db205..754b289 100644 --- a/html/import_frame.html +++ b/html/import_frame.html @@ -1,3 +1,10 @@ +<!-- + Copyright (C) 2021 Wojtek Kosior + Redistribution terms are gathered in the `copyright' file. + + This is not a standalone page. This file is meant to be imported into other + HTML code. + --> <style> .padding_right { padding-right: 0.3em; diff --git a/html/options.html b/html/options.html index 085162c..f2a75e1 100644 --- a/html/options.html +++ b/html/options.html @@ -248,6 +248,7 @@ </div> </div> <button id="add_page_but" type="button"> Add page </button> + <IMPORT html/default_blocking_policy.html /> </div> <div id="bags" class="tab"> <div class="table_wrapper tight_table has_bottom_line has_upper_line"> diff --git a/html/options_main.js b/html/options_main.js index e08bdb9..2f4f154 100644 --- a/html/options_main.js +++ b/html/options_main.js @@ -17,6 +17,7 @@ * IMPORT by_id * IMPORT matchers * IMPORT get_import_frame + * IMPORT init_default_policy_dialog * IMPORTS_END */ @@ -670,6 +671,8 @@ function jump_to_item(url_with_item) async function main() { + init_default_policy_dialog(); + storage = await get_remote_storage(); for (let prefix of list_prefixes) { |