aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWojtek Kosior <koszko@koszko.org>2021-09-02 18:35:49 +0200
committerWojtek Kosior <koszko@koszko.org>2021-09-02 18:35:49 +0200
commit6247f163d3ca89d5570450ac7ac8fd18f73bb74b (patch)
treee3d4817ae475e1f3553d3a50a77792fc5c3c25a0
parent4b59dced912fb9b50ff041c67f0f72cbbad56b6c (diff)
downloadbrowser-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.js6
-rw-r--r--background/page_actions_server.js11
-rwxr-xr-xbuild.sh11
-rw-r--r--common/observable.js28
-rw-r--r--common/storage_light.js129
-rw-r--r--common/storage_raw.js11
-rw-r--r--content/main.js2
-rw-r--r--content/page_actions.js2
-rw-r--r--html/MOZILLA_scrollbar_fix.css46
-rw-r--r--html/base.css8
-rw-r--r--html/default_blocking_policy.html18
-rw-r--r--html/default_blocking_policy.js47
-rw-r--r--html/display-panel.html24
-rw-r--r--html/display-panel.js5
-rw-r--r--html/import_frame.html7
-rw-r--r--html/options.html1
-rw-r--r--html/options_main.js3
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");
}
/*
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 @@
+<!--
+ 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) {