summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHROMIUM_exports_init.js3
-rw-r--r--MOZILLA_exports_init.js57
-rw-r--r--README.md44
-rw-r--r--README.txt33
-rw-r--r--background/broadcast_broker.js24
-rw-r--r--background/main.js50
-rw-r--r--background/page_actions_server.js30
-rw-r--r--background/policy_injector.js20
-rw-r--r--background/storage.js28
-rw-r--r--background/storage_server.js22
-rw-r--r--background/stream_filter.js17
-rwxr-xr-xbuild.sh149
-rw-r--r--common/ajax.js6
-rw-r--r--common/broadcast.js29
-rw-r--r--common/browser.js26
-rw-r--r--common/connection_types.js18
-rw-r--r--common/entities.js15
-rw-r--r--common/indexeddb.js63
-rw-r--r--common/lock.js17
-rw-r--r--common/message_server.js12
-rw-r--r--common/misc.js43
-rw-r--r--common/observables.js (renamed from common/observable.js)18
-rw-r--r--common/once.js6
-rw-r--r--common/patterns.js13
-rw-r--r--common/patterns_query_tree.js27
-rw-r--r--common/sanitize_JSON.js6
-rw-r--r--common/settings_query.js27
-rw-r--r--common/sha256.js8
-rw-r--r--common/storage_client.js21
-rw-r--r--common/storage_light.js39
-rw-r--r--common/storage_raw.js33
-rw-r--r--common/stored_types.js12
-rwxr-xr-x[-rw-r--r--]compute_scripts.awk748
-rw-r--r--content/activity_info_server.js34
-rw-r--r--content/main.js29
-rw-r--r--content/page_actions.js19
-rw-r--r--content/repo_query.js30
-rw-r--r--copyright17
-rw-r--r--html/DOM_helpers.js15
-rw-r--r--html/MOZILLA_scrollbar_fix.css48
-rw-r--r--html/back_button.css28
-rw-r--r--html/base.css29
-rw-r--r--html/default_blocking_policy.html32
-rw-r--r--html/default_blocking_policy.js17
-rw-r--r--html/display_panel.html (renamed from html/display-panel.html)42
-rw-r--r--html/display_panel.js (renamed from html/display-panel.js)68
-rw-r--r--html/import_frame.html31
-rw-r--r--html/import_frame.js21
-rw-r--r--html/mozilla_scrollbar_fix.css67
-rw-r--r--html/options.html38
-rw-r--r--html/options_main.js24
-rw-r--r--html/reset.css3
-rw-r--r--html/table.css29
-rw-r--r--manifest.json53
-rwxr-xr-xprocess_html_file.sh43
-rw-r--r--test/script_loader.py55
-rw-r--r--test/unit/conftest.py8
-rw-r--r--test/unit/test_basic.py6
-rw-r--r--test/unit/test_broadcast.py54
-rw-r--r--test/unit/test_indexeddb.py32
-rw-r--r--test/unit/test_patterns.py12
-rw-r--r--test/unit/test_patterns_query_tree.py52
62 files changed, 1412 insertions, 1188 deletions
diff --git a/CHROMIUM_exports_init.js b/CHROMIUM_exports_init.js
deleted file mode 100644
index 0e61d40..0000000
--- a/CHROMIUM_exports_init.js
+++ /dev/null
@@ -1,3 +0,0 @@
-// SPDX-License-Identifier: CC0-1.0
-
-window.haketilo_exports = {is_chrome: true, browser: window.chrome};
diff --git a/MOZILLA_exports_init.js b/MOZILLA_exports_init.js
deleted file mode 100644
index a1135e8..0000000
--- a/MOZILLA_exports_init.js
+++ /dev/null
@@ -1,57 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-/**
- * This file is part of Haketilo.
- *
- * Function: Data structure to query items by URL patterns.
- *
- * Copyright (C) 2021 Wojtek Kosior
- *
- * 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.
- *
- * As additional permission under GNU GPL version 3 section 7, you
- * may distribute forms of that code without the copy of the GNU
- * GPL normally required by section 4, provided you include this
- * license notice and, in case of non-source distribution, a URL
- * through which recipients can access the Corresponding Source.
- * If you modify file(s) with this exception, you may extend this
- * exception to your version of the file(s), but you are not
- * obligated to do so. If you do not wish to do so, delete this
- * exception statement from your version.
- *
- * As a special exception to the GPL, any HTML file which merely
- * makes function calls to this code, and for that purpose
- * includes it by reference shall be deemed a separate work for
- * copyright law purposes. If you modify this code, you may extend
- * this exception to your version of the code, but you are not
- * obligated to do so. If you do not wish to do so, delete this
- * exception statement from your version.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * I, Wojtek Kosior, thereby promise not to sue for violation of this file's
- * license. Although I request that you do not make use this code in a
- * proprietary program, I am not going to enforce this in court.
- */
-
-/* Polyfill for IceCat 60. */
-String.prototype.matchAll = String.prototype.matchAll || function(regex) {
- if (regex.flags.search("g") === -1)
- throw new TypeError("String.prototype.matchAll called with a non-global RegExp argument");
-
- for (const matches = [];;) {
- if (matches[matches.push(regex.exec(this)) - 1] === null)
- return matches.splice(0, matches.length - 1);
- }
-}
-
-window.haketilo_exports = {is_mozilla: true, browser: this.browser};
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9eed5d5
--- /dev/null
+++ b/README.md
@@ -0,0 +1,44 @@
+[//]: # ( SPDX-License-Identifier: CC0-1.0 )
+
+[//]: # ( Haketilo's README file )
+
+[//]: # ( Copyright (C) 2021 Wojtek Kosior )
+
+[//]: # ( Available under the terms of Creative Commons Zero v1.0 Universal. )
+
+# Haketilo - Make The Web Great Again!
+
+This extension's goal is to allow replacing javascript served by websites
+with scripts specified by user. Something like NoScript and Greasemonkey
+together. Such facility is necessary to enable browsing World Wide Web
+without executing nonfree software.
+
+Currently, the target browsers for this extension are Ungoogled Chromium
+and various forks of Firefox (version 60+).
+
+This extension is still in an early stage. Also see
+[our wiki](https://hydrillabugs.koszko.org/projects/haketilo/wiki/)
+for documentation in development.
+
+## Installation
+The extension can be "built" with `./build.sh mozilla` or `./build.sh chromium`.
+This creates directories build_mozilla/ and build_chromium/, respectively.
+Such directory can be loaded into Ungoogled Chromium or a modern Gecko-based
+browser as unpacked extension.
+
+## Copyright
+All copyright information is gathered in the `copyright` file which follows
+(loosely) the [format of debian/copyright file](https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/). License notices are also present in all text files of the extension.
+
+In general, this entire extension is available under the terms of GPLv3+ with
+various additional licenses and permissions for particular files.
+
+I, Wojtek Kosior, thereby promise not to sue for violation of this program's
+licenses. Although I request that you do not make use this code in a proprietary
+program, I am not going to enforce this in court.
+
+## Contributing
+Get the code from: https://git.koszko.org/browser-extension/
+Come to: https://hydrillabugs.koszko.org/projects/haketilo
+
+Optionally, write to `$(echo a29zemtvQGtvc3prby5vcmcK | base64 -d)`
diff --git a/README.txt b/README.txt
deleted file mode 100644
index 1aec0ba..0000000
--- a/README.txt
+++ /dev/null
@@ -1,33 +0,0 @@
-# Haketilo - Make The Web Great Again! #
-
-This extension's goal is to allow replacing javascript served by websites
-with scripts specified by user. Something like NoScript and Greasemonkey
-together. Such facility is necessary to enable browsing World Wide Web
-without executing nonfree software.
-
-Currently, the target browsers for this extension are Ungoogled Chromium
-and various forks of Firefox (version 60+).
-
-This extension is still in an early stage. Also see
-`https://hydrillabugs.koszko.org/projects/haketilo/wiki/' for documentation in
-development.
-
-## Installation ##
-The extension can be "built" with `./build.sh mozilla' or `./build.sh chromium'.
-This creates directories build_mozilla/ and build_chromium/, respectively.
-Such directory can be loaded into Ungoogled Chromium or a modern Gecko-based
-browser as unpacked extension.
-
-## Copyright ##
-All copyright information is gathered in the `copyright' file which follows
-(loosely) the format of debian/copyright file described at
-`https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/'
-
-In general, this entire extension is available under the terms of GPLv3+ with
-various additional licenses and permissions for particular files.
-
-## Contributing ##
-Get the code from: https://git.koszko.org/browser-extension/
-Come to: https://hydrillabugs.koszko.org/projects/haketilo
-
-Optionally, write to $(echo a29zemtvQGtvc3prby5vcmcK | base64 -d)
diff --git a/background/broadcast_broker.js b/background/broadcast_broker.js
index 7af8769..9847d7e 100644
--- a/background/broadcast_broker.js
+++ b/background/broadcast_broker.js
@@ -42,12 +42,9 @@
* proprietary program, I am not going to enforce this in court.
*/
-/*
- * IMPORTS_START
- * IMPORT listen_for_connection
- * IMPORT CONNECTION_TYPE
- * IMPORTS_END
- */
+#IMPORT common/connection_types.js AS CONNECTION_TYPE
+
+#FROM common/message_server.js IMPORT listen_for_connection
let next_id = 1;
@@ -55,7 +52,7 @@ const listeners_by_channel = new Map();
function new_broadcast_listener(port)
{
- listener_ctx = {port, id: ++next_id, channels: new Set()};
+ const listener_ctx = {port, id: ++next_id, channels: new Set()};
port.onMessage.addListener(msg => listener_command(msg, listener_ctx));
port.onDisconnect.addListener(msg => listener_remove(msg, listener_ctx));
}
@@ -102,7 +99,7 @@ function remove_broadcast_listener(listener_ctx)
function new_broadcast_sender(port)
{
- sender_ctx = {prepared_broadcasts: new Set()};
+ const sender_ctx = {prepared_broadcasts: new Set()};
port.onMessage.addListener(msg => sender_command(msg, sender_ctx));
port.onDisconnect.addListener(msg => flush(sender_ctx));
}
@@ -125,7 +122,7 @@ function sender_command(msg, sender_ctx)
function prepare(sender_ctx, channel_name, value, timeout)
{
- broadcast_data = [channel_name, value];
+ const broadcast_data = [channel_name, value];
sender_ctx.prepared_broadcasts.add(broadcast_data);
if (timeout === 0)
@@ -170,15 +167,10 @@ function remove_broadcast_sender(sender_ctx)
sender_ctx.prepared_broadcasts.forEach(nv => broadcast(...nv));
}
-function start_broadcast_broker()
+function start()
{
listen_for_connection(CONNECTION_TYPE.BROADCAST_SEND, new_broadcast_sender);
listen_for_connection(CONNECTION_TYPE.BROADCAST_LISTEN,
new_broadcast_listener);
}
-
-/*
- * EXPORTS_START
- * EXPORT start_broadcast_broker
- * EXPORTS_END
- */
+#EXPORT start
diff --git a/background/main.js b/background/main.js
index 2809334..cff0786 100644
--- a/background/main.js
+++ b/background/main.js
@@ -42,26 +42,25 @@
* proprietary program, I am not going to enforce this in court.
*/
-/*
- * IMPORTS_START
- * IMPORT initial_data
- * IMPORT TYPE_PREFIX
- * IMPORT get_storage
- * IMPORT light_storage
- * IMPORT start_storage_server
- * IMPORT start_page_actions_server
- * IMPORT browser
- * IMPORT is_privileged_url
- * IMPORT query_best
- * IMPORT inject_csp_headers
- * IMPORT apply_stream_filter
- * IMPORT is_chrome
- * IMPORT is_mozilla
- * IMPORTS_END
- */
+#IMPORT common/storage_light.js AS light_storage
+
+#IMPORT background/storage_server.js
+#IMPORT background/page_actions_server.js
+#IMPORT background/stream_filter.js
+
+#FROM common/browser.js IMPORT browser
+#FROM common/stored_types.js IMPORT TYPE_PREFIX
+#FROM background/storage.js IMPORT get_storage
+#FROM common/misc.js IMPORT is_privileged_url
+#FROM common/settings_query.js IMPORT query_best
+#FROM background/policy_injector.js IMPORT inject_csp_headers
+
+const initial_data = (
+#INCLUDE_VERBATIM default_settings.json
+);
-start_storage_server();
-start_page_actions_server();
+storage_server.start();
+page_actions_server.start();
async function init_ext(install_details)
{
@@ -131,7 +130,7 @@ function sanitize_web_page(details)
if (!skip) {
/* Check for API availability. */
if (browser.webRequest.filterResponseData)
- headers = apply_stream_filter(details, headers, policy);
+ headers = stream_filter.apply(details, headers, policy);
}
return {responseHeaders: headers};
@@ -192,9 +191,11 @@ async function start_webRequest_operations()
{
storage = await get_storage();
+#IF CHROMIUM
+ const extra_opts = ["blocking", "extraHeaders"];
+#ELSE
const extra_opts = ["blocking"];
- if (is_chrome)
- extra_opts.push("extraHeaders");
+#ENDIF
browser.webRequest.onHeadersReceived.addListener(
sanitize_web_page,
@@ -214,6 +215,7 @@ async function start_webRequest_operations()
start_webRequest_operations();
+#IF MOZILLA
const code = `\
console.warn("Hi, I'm Mr Dynamic!");
@@ -232,5 +234,5 @@ async function test_dynamic_content_scripts()
});
}
-if (is_mozilla)
- test_dynamic_content_scripts();
+test_dynamic_content_scripts();
+#ENDIF
diff --git a/background/page_actions_server.js b/background/page_actions_server.js
index bb4c34f..578d1b1 100644
--- a/background/page_actions_server.js
+++ b/background/page_actions_server.js
@@ -41,18 +41,15 @@
* proprietary program, I am not going to enforce this in court.
*/
-/*
- * IMPORTS_START
- * IMPORT get_storage
- * IMPORT light_storage
- * IMPORT TYPE_PREFIX
- * IMPORT CONNECTION_TYPE
- * IMPORT browser
- * IMPORT listen_for_connection
- * IMPORT sha256
- * IMPORT make_ajax_request
- * IMPORTS_END
- */
+#IMPORT common/storage_light.js AS light_storage
+#IMPORT common/connection_types.js AS CONNECTION_TYPE
+
+#FROM common/browser.js IMPORT browser
+#FROM common/message_server.js IMPORT listen_for_connection
+#FROM background/storage.js IMPORT get_storage
+#FROM common/stored_types.js IMPORT TYPE_PREFIX
+#FROM common/sha256.js IMPORT sha256
+#FROM common/ajax.js IMPORT make_ajax_request
var storage;
var handler;
@@ -143,15 +140,10 @@ function new_connection(port)
port.onMessage.addListener(handler[0]);
}
-async function start_page_actions_server()
+async function start()
{
storage = await get_storage();
listen_for_connection(CONNECTION_TYPE.PAGE_ACTIONS, new_connection);
}
-
-/*
- * EXPORTS_START
- * EXPORT start_page_actions_server
- * EXPORTS_END
- */
+#EXPORT start
diff --git a/background/policy_injector.js b/background/policy_injector.js
index 787f1f0..2544e8e 100644
--- a/background/policy_injector.js
+++ b/background/policy_injector.js
@@ -43,14 +43,12 @@
* proprietary program, I am not going to enforce this in court.
*/
-/*
- * IMPORTS_START
- * IMPORT make_csp_rule
- * IMPORT csp_header_regex
- * Re-enable the import below once nonce stuff here is ready
- * !mport gen_nonce
- * IMPORTS_END
- */
+#FROM common/misc.js IMPORT make_csp_rule, csp_header_regex
+
+/* Re-enable the import below once nonce stuff here is ready */
+#IF NEVER
+#FROM common/misc.js IMPORT gen_nonce
+#ENDIF
function inject_csp_headers(headers, policy)
{
@@ -76,8 +74,4 @@ function inject_csp_headers(headers, policy)
return headers;
}
-/*
- * EXPORTS_START
- * EXPORT inject_csp_headers
- * EXPORTS_END
- */
+#EXPORT inject_csp_headers
diff --git a/background/storage.js b/background/storage.js
index d57c701..2a93b87 100644
--- a/background/storage.js
+++ b/background/storage.js
@@ -41,19 +41,13 @@
* proprietary program, I am not going to enforce this in court.
*/
-/*
- * IMPORTS_START
- * IMPORT raw_storage
- * IMPORT TYPE_NAME
- * IMPORT list_prefixes
- * IMPORT make_lock
- * IMPORT lock
- * IMPORT unlock
- * IMPORT make_once
- * IMPORT browser
- * IMPORT observables
- * IMPORTS_END
- */
+#IMPORT common/storage_raw.js AS raw_storage
+#IMPORT common/observables.js
+
+#FROM common/stored_types.js IMPORT list_prefixes, TYPE_NAME
+#FROM common/lock.js IMPORT lock, unlock, make_lock
+#FROM common/once.js IMPORT make_once
+#FROM common/browser.js IMPORT browser
var exports = {};
@@ -362,10 +356,4 @@ exports.clear = async function ()
unlock(list.lock);
}
-const get_storage = make_once(init);
-
-/*
- * EXPORTS_START
- * EXPORT get_storage
- * EXPORTS_END
- */
+#EXPORT make_once(init) AS get_storage
diff --git a/background/storage_server.js b/background/storage_server.js
index 9355f86..2d13690 100644
--- a/background/storage_server.js
+++ b/background/storage_server.js
@@ -41,14 +41,11 @@
* proprietary program, I am not going to enforce this in court.
*/
-/*
- * IMPORTS_START
- * IMPORT listen_for_connection
- * IMPORT get_storage
- * IMPORT list_prefixes
- * IMPORT CONNECTION_TYPE
- * IMPORTS_END
- */
+#IMPORT common/connection_types.js AS CONNECTION_TYPE
+
+#FROM common/message_server.js IMPORT listen_for_connection
+#FROM background/storage.js IMPORT get_storage
+#FROM common/stored_types.js IMPORT list_prefixes
var storage;
@@ -89,15 +86,10 @@ function new_connection(port)
remove_storage_listener(handle_change));
}
-async function start_storage_server()
+async function start()
{
storage = await get_storage();
listen_for_connection(CONNECTION_TYPE.REMOTE_STORAGE, new_connection);
}
-
-/*
- * EXPORTS_START
- * EXPORT start_storage_server
- * EXPORTS_END
- */
+#EXPORT start
diff --git a/background/stream_filter.js b/background/stream_filter.js
index 9d8e1e5..925a39d 100644
--- a/background/stream_filter.js
+++ b/background/stream_filter.js
@@ -29,12 +29,8 @@
* in LibreJS.
*/
-/*
- * IMPORTS_START
- * IMPORT browser
- * IMPORT csp_header_regex
- * IMPORTS_END
- */
+#FROM common/browser.js IMPORT browser
+#FROM common/misc.js IMPORT csp_header_regex
function validate_encoding(charset)
{
@@ -204,7 +200,7 @@ function filter_data(properties, event)
properties.filter.disconnect();
}
-function apply_stream_filter(details, headers, policy)
+function apply(details, headers, policy)
{
if (!policy.payload)
return headers;
@@ -224,9 +220,4 @@ function apply_stream_filter(details, headers, policy)
*/
return headers;
}
-
-/*
- * EXPORTS_START
- * EXPORT apply_stream_filter
- * EXPORTS_END
- */
+#EXPORT apply
diff --git a/build.sh b/build.sh
index d0958a1..8d5b97e 100755
--- a/build.sh
+++ b/build.sh
@@ -17,137 +17,18 @@ set -e
. ./shell_utils.sh
-as_json_list() {
- while true; do
- if [ "x" = "x$2" ]; then
- printf '\\n\t\t"%s"\\n\t' "$1"
- return
- fi
- printf '\\n\t\t"%s",' "$1"
- shift
- done
-}
-
-as_html_list() {
- while [ "x" != "x$1" ]; do
- printf '\\n <script src="/%s"></script>' "$1"
- shift
- done
-}
-
-compute_scripts() {
- local DIRS="$1"
- local ROOT_SCRIPT="$2"
-
- local AVAILABLE="$(find $DIRS -name '[^.#]*.js')"
-
- awk -f compute_scripts.awk script_dependencies "$ROOT_SCRIPT" $AVAILABLE
-}
-
-build_main() {
- local ALL_SCRIPTDIRS='background html common content'
-
- local ALL_SCRIPTS_AVAILABLE="$(find $ALL_SCRIPTDIRS -name '[^.#]*.js')"
-
- local SCRIPT
- for SCRIPT in $ALL_SCRIPTS_AVAILABLE; do
- map_set SCRIPTS_UNUSED $(sanitize $SCRIPT) yes
- done
-
- local ROOT=background/main.js
- local SCRIPTS_BG="$( compute_scripts 'common/ background/' $ROOT)"
-
- local ROOT=content/main.js
- local SCRIPTS_CONTENT="$( compute_scripts 'common/ content/' $ROOT)"
-
- local ROOT=html/display-panel.js
- local SCRIPTS_POPUP="$( compute_scripts 'common/ html/' $ROOT)"
-
- local ROOT=html/options_main.js
- local SCRIPTS_OPTIONS="$( compute_scripts 'common/ html/' $ROOT)"
-
- local BGSCRIPTS="$( as_json_list $SCRIPTS_BG )"
- local CONTENTSCRIPTS="$( as_json_list $SCRIPTS_CONTENT )"
- local POPUPSCRIPTS="$( as_html_list $SCRIPTS_POPUP )"
- local OPTIONSSCRIPTS="$( as_html_list $SCRIPTS_OPTIONS )"
-
- for SCRIPT in $SCRIPTS_BG $SCRIPTS_CONTENT $SCRIPTS_POPUP $SCRIPTS_OPTIONS
- do
- map_del SCRIPTS_UNUSED $(sanitize $SCRIPT)
- done
-
- for DIR in $(find $ALL_SCRIPTDIRS -type d); do
- mkdir -p "$BUILDDIR"/$DIR
- done
-
- CHROMIUM_UPDATE_URL=''
- GECKO_APPLICATIONS=''
-
- if [ "x$UPDATE_URL" != x ]; then
- UPDATE_URL=",\n \"update_url\": \"$UPDATE_URL\""
- fi
-
- if [ "$BROWSER" = "chromium" ]; then
- CHROMIUM_UPDATE_URL="$UPDATE_URL"
- else
- GECKO_APPLICATIONS="\n\
- \"applications\": {\n\
- \"gecko\": {\n\
- \"id\": \"{6fe13369-88e9-440f-b837-5012fb3bedec}\",\n\
- \"strict_min_version\": \"60.0\"$UPDATE_URL\n\
- }\n\
- },"
- fi
-
- sed "\
-s^_GECKO_APPLICATIONS_^$GECKO_APPLICATIONS^
-s^_CHROMIUM_UPDATE_URL_^$CHROMIUM_UPDATE_URL^
-s^_BGSCRIPTS_^$BGSCRIPTS^
-s^_CONTENTSCRIPTS_^$CONTENTSCRIPTS^" \
- < manifest.json > "$BUILDDIR"/manifest.json
-
- ./process_html_file.sh html/display-panel.html |
- sed "s^_POPUPSCRIPTS_^$POPUPSCRIPTS^" \
- > "$BUILDDIR"/html/display-panel.html
-
- ./process_html_file.sh html/options.html |
- sed "s^_OPTIONSSCRIPTS_^$OPTIONSSCRIPTS^" \
- > "$BUILDDIR"/html/options.html
-
- for FILE in $ALL_SCRIPTS_AVAILABLE; do
- FILEKEY=$(sanitize "$FILE")
- if [ "x$(map_get SCRIPTS_UNUSED $FILEKEY)" = "xyes" ]; then
- printf 'WARNING! %s not used\n' "$FILE" >&2
- else
- awk -f compute_scripts.awk wrapped_code "$FILE" > "$BUILDDIR"/$FILE
- fi
- done
-
- ./write_exports_init.sh "$BROWSER" "$BUILDDIR" default_settings.json
-
- cp -r copyright licenses/ "$BUILDDIR"
- cp dummy "$BUILDDIR"
- 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
- printf '\n' > "$MOZILLA_FILE"
- done
- fi
- if [ "$BROWSER" = "mozilla" ]; then
- for CHROMIUM_FILE in $(find "$BUILDDIR" -name "CHROMIUM_*"); do
- printf '\n' > "$CHROMIUM_FILE"
- done
- fi
-}
-
print_usage() {
printf 'usage: %s mozilla|chromium [source directory] [update url]\n' \
"$0" >&2
}
+call_awk() {
+ local BROWSER_UPCASE="$(printf %s "$BROWSER" | tr '[:lower:]' '[:upper:]')"
+ nawk -f compute_scripts.awk -- -M manifest.json -D "$BROWSER_UPCASE" \
+ -D MV2 --output=files-to-copy --write-js-deps --write-html-deps \
+ --output-dir="$BUILDDIR"
+}
+
main() {
if [ "x$1" = "xmozilla" -o "x$1" = "xchromium" ]; then
BROWSER=$1
@@ -168,8 +49,22 @@ main() {
fi
UPDATE_URL="$3"
+ if [ -n "$3" ]; then
+ printf 'possibility of using an update url is currently unimplemented' \
+ >&2
+ exit 1
+ fi
+
+ FILES_TO_COPY="$(call_awk)"
+ for FILE in $FILES_TO_COPY; do
+ FILEDIR="$(printf %s "$FILE" | sed 's_[^/]*$_/_')"
+ if [ -n "$FILEDIR" -a ! -d "$BUILDDIR/$FILEDIR" ]; then
+ mkdir -p "$BUILDDIR/$FILEDIR"
+ fi
+ cp "$FILE" "$BUILDDIR/$FILE"
+ done
- build_main
+ cp -r README.md copyright licenses/ "$BUILDDIR"
}
main "$@"
diff --git a/common/ajax.js b/common/ajax.js
index d61faa6..4d0e630 100644
--- a/common/ajax.js
+++ b/common/ajax.js
@@ -67,8 +67,4 @@ function make_ajax_request(method, url)
initiate_ajax_request(resolve, reject, method, url));
}
-/*
- * EXPORTS_START
- * EXPORT make_ajax_request
- * EXPORTS_END
- */
+#EXPORT make_ajax_request
diff --git a/common/broadcast.js b/common/broadcast.js
index cc11a20..b69f352 100644
--- a/common/broadcast.js
+++ b/common/broadcast.js
@@ -41,11 +41,9 @@
* proprietary program, I am not going to enforce this in court.
*/
-/*
- * IMPORTS_START
- * IMPORT CONNECTION_TYPE
- * IMPORTS_END
- */
+#IMPORT common/connection_types.js AS CONNECTION_TYPE
+
+#FROM common/browser.js IMPORT browser
function sender_connection()
{
@@ -53,11 +51,13 @@ function sender_connection()
port: browser.runtime.connect({name: CONNECTION_TYPE.BROADCAST_SEND})
};
}
+#EXPORT sender_connection
function out(sender_conn, channel_name, value)
{
sender_conn.port.postMessage(["broadcast", channel_name, value]);
}
+#EXPORT out
/*
* prepare()'d message will be broadcasted if the connection is closed or when
@@ -77,16 +77,19 @@ function prepare(sender_conn, channel_name, value, timeout=5000)
{
sender_conn.port.postMessage(["prepare", channel_name, value, timeout]);
}
+#EXPORT prepare
function discard(sender_conn)
{
sender_conn.port.postMessage(["discard"]);
}
+#EXPORT discard
function flush(sender_conn)
{
sender_conn.port.postMessage(["flush"]);
}
+#EXPORT flush
function listener_connection(cb)
{
@@ -98,30 +101,22 @@ function listener_connection(cb)
return conn;
}
+#EXPORT listener_connection
function subscribe(listener_conn, channel_name)
{
listener_conn.port.postMessage(["subscribe", channel_name]);
}
+#EXPORT subscribe
function unsubscribe(listener_conn, channel_name)
{
listener_conn.port.postMessage(["unsubscribe", channel_name]);
}
+#EXPORT unsubscribe
function close(conn)
{
conn.port.disconnect();
}
-
-const broadcast = {
- sender_connection, out, prepare, discard, flush,
- listener_connection, subscribe, unsubscribe,
- close
-};
-
-/*
- * EXPORTS_START
- * EXPORT broadcast
- * EXPORTS_END
- */
+#EXPORT close
diff --git a/common/browser.js b/common/browser.js
new file mode 100644
index 0000000..4830774
--- /dev/null
+++ b/common/browser.js
@@ -0,0 +1,26 @@
+/**
+ * This file is part of Haketilo.
+ *
+ * Function: Export the browser API object.
+ *
+ * Copyright (C) 2021 Wojtek Kosior
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the CC0 1.0 Universal License as published by
+ * the Creative Commons Corporation.
+ *
+ * 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
+ * CC0 1.0 Universal License for more details.
+ */
+
+#IF MOZILLA
+
+#EXPORT globalThis.browser AS browser
+
+#ELIF CHROMIUM
+
+#EXPORT chrome AS browser
+
+#ENDIF
diff --git a/common/connection_types.js b/common/connection_types.js
index 9747e5c..0b83c2b 100644
--- a/common/connection_types.js
+++ b/common/connection_types.js
@@ -46,16 +46,8 @@
* to browser.runtime.connect()
*/
-const CONNECTION_TYPE = {
- REMOTE_STORAGE : "0",
- PAGE_ACTIONS : "1",
- ACTIVITY_INFO : "2",
- BROADCAST_SEND: "3",
- BROADCAST_LISTEN: "4"
-};
-
-/*
- * EXPORTS_START
- * EXPORT CONNECTION_TYPE
- * EXPORTS_END
- */
+#EXPORT "0" AS REMOTE_STORAGE
+#EXPORT "1" AS PAGE_ACTIONS
+#EXPORT "2" AS ACTIVITY_INFO
+#EXPORT "3" AS BROADCAST_SEND
+#EXPORT "4" AS BROADCAST_LISTEN
diff --git a/common/entities.js b/common/entities.js
index 3a1346a..29e130c 100644
--- a/common/entities.js
+++ b/common/entities.js
@@ -80,6 +80,7 @@ function get_newest_version(versioned_item)
const best_ver = max(Object.keys(versioned_item).map(parse_version));
return versioned_item[version_string(best_ver)];
}
+#EXPORT get_newest_version AS get_newest
/*
* item is a definition of a resource or mapping. Yield all file references
@@ -95,17 +96,9 @@ function* get_used_files(item)
yield file;
}
}
+#EXPORT get_used_files AS get_files
-const entities = {
- get_newest: get_newest_version,
- get_files: get_used_files
-};
-
-/*
- * EXPORTS_START
- * EXPORT entities
- * EXPORTS_END
- */
+#IF NEVER
/*
* Note: the functions below were overeagerly written and are not used now but
@@ -146,3 +139,5 @@ const version_reductor = (acc, n) => [...(n || acc.length ? [n] : []), ...acc];
* Returns a *new* array. Doesn't modify its argument.
*/
const normalize_version = ver => Array.reduceRight(ver, version_reductor, []);
+
+#ENDIF
diff --git a/common/indexeddb.js b/common/indexeddb.js
index 1741c91..c97c115 100644
--- a/common/indexeddb.js
+++ b/common/indexeddb.js
@@ -41,13 +41,12 @@
* proprietary program, I am not going to enforce this in court.
*/
-/*
- * IMPORTS_START
- * IMPORT initial_data
- * IMPORT entities
- * IMPORT broadcast
- * IMPORTS_END
- */
+#IMPORT common/entities.js
+#IMPORT common/broadcast.js
+
+let initial_data = (
+#INCLUDE_VERBATIM default_settings.json
+);
/* Update when changes are made to database schema. Must have 3 elements */
const db_version = [1, 0, 0];
@@ -79,6 +78,7 @@ async function idb_get(transaction, store_name, key)
const req = transaction.objectStore(store_name).get(key);
return (await wait_request(req)).target.result;
}
+#EXPORT idb_get
/* asynchronous wrapper for IDBObjectStore's put() method. */
async function idb_put(transaction, store_name, object)
@@ -132,6 +132,7 @@ async function get_db()
return db;
}
+#EXPORT get_db AS get
/* Helper function used by make_context(). */
function reject_discard(context)
@@ -177,10 +178,11 @@ function make_context(transaction, files)
*/
async function start_items_transaction(item_store_names, files)
{
- const db = await haketilodb.get();
+ const db = await get_db();
const scope = [...item_store_names, "files", "file_uses"];
return make_context(db.transaction(scope, "readwrite"), files);
}
+#EXPORT start_items_transaction
async function incr_file_uses(context, file_ref, by=1)
{
@@ -242,6 +244,7 @@ async function finalize_items_transaction(context)
return context.result;
}
+#EXPORT finalize_items_transaction
/*
* How a sample data argument to the function below might look like:
@@ -287,6 +290,7 @@ async function save_items(data)
return _save_items(data.resources, data.mappings, context);
}
+#EXPORT save_items
async function _save_items(resources, mappings, context)
{
@@ -322,6 +326,7 @@ async function save_item(item, context)
await _remove_item(store_name, item.identifier, context, false);
await idb_put(context.transaction, store_name, item);
}
+#EXPORT save_item
/* Helper function used by remove_item() and save_item(). */
async function _remove_item(store_name, identifier, context)
@@ -348,11 +353,16 @@ async function remove_item(store_name, identifier, context)
await idb_del(context.transaction, store_name, identifier);
}
+const remove_resource = (id, ctx) => remove_item("resources", id, ctx);
+#EXPORT remove_resource
+
+const remove_mapping = (id, ctx) => remove_item("mappings", id, ctx);
+#EXPORT remove_mapping
+
/* Callback used when listening to broadcasts while tracking db changes. */
async function track_change(tracking, identifier)
{
- const transaction =
- (await haketilodb.get()).transaction([tracking.store_name]);
+ const transaction = (await get_db()).transaction([tracking.store_name]);
const new_val = await idb_get(transaction, tracking.store_name, identifier);
tracking.onchange({identifier, new_val});
@@ -373,7 +383,7 @@ async function track_change(tracking, identifier)
* }
*
* Returns a [tracking, all_current_items] array where `tracking` is an object
- * that can be later passed to haketilodb.untrack() to stop tracking changes and
+ * that can be later passed to untrack() to stop tracking changes and
* `all_current_items` is an array of items currently present in the object
* store.
*
@@ -388,33 +398,18 @@ async function track(store_name, onchange)
broadcast.listener_connection(msg => track_change(tracking, msg[1]));
broadcast.subscribe(tracking.listener, `idb_changes_${store_name}`);
- const transaction = (await haketilodb.get()).transaction([store_name]);
+ const transaction = (await get_db()).transaction([store_name]);
const all_req = transaction.objectStore(store_name).getAll();
return [tracking, (await wait_request(all_req)).target.result];
}
-function untrack(tracking)
-{
- broadcast.close(tracking.listener);
-}
+const track_resources = onchange => track("resources", onchange);
+#EXPORT track_resources
-const haketilodb = {
- get: get_db,
- save_items,
- save_item,
- remove_resource: (id, ctx) => remove_item("resources", id, ctx),
- remove_mapping: (id, ctx) => remove_item("mappings", id, ctx),
- start_items_transaction,
- finalize_items_transaction,
- track_resources: onchange => track("resources", onchange),
- track_mappings: onchange => track("mappings", onchange),
- untrack
-};
+const track_mappings = onchange => track("mappings", onchange);
+#EXPORT track_mappings
+
+const untrack = tracking => broadcast.close(tracking.listener);
+#EXPORT untrack
-/*
- * EXPORTS_START
- * EXPORT haketilodb
- * EXPORT idb_get
- * EXPORTS_END
- */
diff --git a/common/lock.js b/common/lock.js
index d136469..56dad4f 100644
--- a/common/lock.js
+++ b/common/lock.js
@@ -55,9 +55,7 @@
* in a promise.
*/
-function make_lock() {
- return {free: true, queue: []};
-}
+#EXPORT () => ({free: true, queue: []}) AS make_lock
function _lock(lock, cb) {
if (lock.free) {
@@ -68,9 +66,7 @@ function _lock(lock, cb) {
}
}
-function lock(lock) {
- return new Promise((resolve, reject) => _lock(lock, resolve));
-}
+#EXPORT lock => new Promise(resolve => _lock(lock, resolve)) AS lock
function unlock(lock) {
if (lock.free)
@@ -84,11 +80,4 @@ function unlock(lock) {
setTimeout(cb);
}
}
-
-/*
- * EXPORTS_START
- * EXPORT make_lock
- * EXPORT lock
- * EXPORT unlock
- * EXPORTS_END
- */
+#EXPORT unlock
diff --git a/common/message_server.js b/common/message_server.js
index cd9a4d8..fd609c7 100644
--- a/common/message_server.js
+++ b/common/message_server.js
@@ -41,11 +41,7 @@
* proprietary program, I am not going to enforce this in court.
*/
-/*
- * IMPORTS_START
- * IMPORT browser
- * IMPORTS_END
- */
+#FROM common/browser.js IMPORT browser
var listeners = {};
@@ -66,8 +62,4 @@ function raw_listen(port)
browser.runtime.onConnect.addListener(raw_listen);
-/*
- * EXPORTS_START
- * EXPORT listen_for_connection
- * EXPORTS_END
- */
+#EXPORT listen_for_connection
diff --git a/common/misc.js b/common/misc.js
index 4d4b346..dc4a598 100644
--- a/common/misc.js
+++ b/common/misc.js
@@ -42,21 +42,8 @@
* proprietary program, I am not going to enforce this in court.
*/
-/*
- * IMPORTS_START
- * IMPORT browser
- * IMPORT TYPE_NAME
- * IMPORT TYPE_PREFIX
- * IMPORTS_END
- */
-
-/* Generate a random base64-encoded 128-bit sequence */
-function gen_nonce()
-{
- let randomData = new Uint8Array(16);
- crypto.getRandomValues(randomData);
- return btoa(String.fromCharCode.apply(null, randomData));
-}
+#FROM common/browser.js IMPORT browser
+#FROM common/stored_types.js IMPORT TYPE_NAME, TYPE_PREFIX
/*
* generating unique, per-site value that can be computed synchronously
@@ -78,6 +65,7 @@ function gen_nonce(length=16)
crypto.getRandomValues(randomData);
return Uint8toHex(randomData);
}
+#EXPORT gen_nonce
/* CSP rule that blocks scripts according to policy's needs. */
function make_csp_rule(policy)
@@ -88,19 +76,18 @@ function make_csp_rule(policy)
rule += ` script-src ${script_src}; script-src-elem ${script_src};`;
return rule;
}
+#EXPORT make_csp_rule
/* Check if some HTTP header might define CSP rules. */
const csp_header_regex =
/^\s*(content-security-policy|x-webkit-csp|x-content-security-policy)/i;
+#EXPORT csp_header_regex
/*
* Print item together with type, e.g.
* nice_name("s", "hello") → "hello (script)"
*/
-function nice_name(prefix, name)
-{
- return `${name} (${TYPE_NAME[prefix]})`;
-}
+#EXPORT (prefix, name) => `${name} (${TYPE_NAME[prefix]})` AS nice_name
/* Open settings tab with given item's editing already on. */
function open_in_settings(prefix, name)
@@ -109,6 +96,7 @@ function open_in_settings(prefix, name)
const url = browser.runtime.getURL("html/options.html#" + prefix + name);
window.open(url, "_blank");
}
+#EXPORT open_in_settings
/*
* Check if url corresponds to a browser's special page (or a directory index in
@@ -116,7 +104,7 @@ function open_in_settings(prefix, name)
*/
const privileged_reg =
/^(chrome(-extension)?|moz-extension):\/\/|^about:|^file:\/\/.*\/$/;
-const is_privileged_url = url => privileged_reg.test(url);
+#EXPORT url => privileged_reg.test(url) AS is_privileged_url
/* Parse a CSP header */
function parse_csp(csp) {
@@ -148,6 +136,7 @@ const matchers = {
nonempty_string_matcher
]
};
+#EXPORT matchers
/*
* Facilitates checking if there aren't any keys in object. This does *NOT*
@@ -159,16 +148,4 @@ function is_object_empty(object)
return false;
return true;
}
-
-/*
- * EXPORTS_START
- * EXPORT gen_nonce
- * EXPORT make_csp_rule
- * EXPORT csp_header_regex
- * EXPORT nice_name
- * EXPORT open_in_settings
- * EXPORT is_privileged_url
- * EXPORT matchers
- * EXPORT is_object_empty
- * EXPORTS_END
- */
+#EXPORT is_object_empty
diff --git a/common/observable.js b/common/observables.js
index 56d0ed6..f1db88c 100644
--- a/common/observable.js
+++ b/common/observables.js
@@ -41,13 +41,16 @@
* proprietary program, I am not going to enforce this in court.
*/
-const make = (value=undefined) => ({value, listeners: new Set()});
-const subscribe = (observable, cb) => observable.listeners.add(cb);
-const unsubscribe = (observable, cb) => observable.listeners.delete(cb);
+#EXPORT (value=undefined) => ({value, listeners: new Set()}) AS make
+#EXPORT (observable, cb) => observable.listeners.add(cb) AS subscribe
+#EXPORT (observable, cb) => observable.listeners.delete(cb) AS unsubscribe
const silent_set = (observable, value) => observable.value = value;
+#EXPORT silent_set
+
const broadcast = (observable, ...values) =>
observable.listeners.forEach(cb => cb(...values));
+#EXPORT broadcast
function set(observable, value)
{
@@ -55,11 +58,4 @@ function set(observable, value)
silent_set(observable, value);
broadcast(observable, value, old_value);
}
-
-const observables = {make, subscribe, unsubscribe, broadcast, silent_set, set};
-
-/*
- * EXPORTS_START
- * EXPORT observables
- * EXPORTS_END
- */
+#EXPORT set
diff --git a/common/once.js b/common/once.js
index 5a62b09..6e82528 100644
--- a/common/once.js
+++ b/common/once.js
@@ -71,8 +71,4 @@ function make_once(result_producer)
return () => get_result(state);
}
-/*
- * EXPORTS_START
- * EXPORT make_once
- * EXPORTS_END
- */
+#EXPORT make_once
diff --git a/common/patterns.js b/common/patterns.js
index 7d28dfe..0b1c3ad 100644
--- a/common/patterns.js
+++ b/common/patterns.js
@@ -72,9 +72,9 @@ function match_or_throw(regex, string, error_msg)
function deconstruct_url(url, use_limits=true)
{
- const max = MAX;
+ const max = Object.assign({}, MAX);
if (!use_limits) {
- for (key in MAX)
+ for (const key in MAX)
max[key] = Infinity;
}
@@ -129,6 +129,7 @@ function deconstruct_url(url, use_limits=true)
return deco;
}
+#EXPORT deconstruct_url
function* each_domain_pattern(deco)
{
@@ -183,10 +184,4 @@ function* each_url_pattern(url)
yield `${deco.proto}://${domain}${path}`;
}
}
-
-/*
- * EXPORTS_START
- * EXPORT each_url_pattern
- * EXPORT deconstruct_url
- * EXPORTS_END
- */
+#EXPORT each_url_pattern
diff --git a/common/patterns_query_tree.js b/common/patterns_query_tree.js
index 49205c5..1bbdb39 100644
--- a/common/patterns_query_tree.js
+++ b/common/patterns_query_tree.js
@@ -41,17 +41,16 @@
* proprietary program, I am not going to enforce this in court.
*/
-/*
- * IMPORTS_START
- * IMPORT deconstruct_url
- * IMPORTS_END
- */
+#FROM common/patterns.js IMPORT deconstruct_url
/* "Pattern Tree" is how we refer to the data structure used for querying
* Haketilo patterns. Those look like 'https://*.example.com/ab/***'. The goal
* is to make it possible for given URL to quickly retrieve all known patterns
* that match it.
*/
+const pattern_tree_make = () => ({})
+#EXPORT pattern_tree_make AS make
+
function empty_node() {
return {
wildcard_matches: [null, null, null],
@@ -134,8 +133,6 @@ function modify_sequence(tree_node, segments, item_modifier)
let removed = true;
for (var current_segment of segments) {
- wildcards = tree_node.wildcard_matches;
-
const child = tree_node.children[current_segment] || empty_node();
tree_node.children[current_segment] = child;
tree_node = child;
@@ -216,6 +213,7 @@ function pattern_tree_register(patterns_by_proto, pattern, item_name, item)
const add_item = obj => Object.assign(obj || {}, {[item_name]: item});
modify_tree(patterns_by_proto, pattern, add_item);
}
+#EXPORT pattern_tree_register AS register
/* Helper function for pattern_tree_deregister(). */
function _remove_item(obj, item_name)
@@ -240,6 +238,7 @@ function pattern_tree_deregister(patterns_by_proto, pattern, item_name)
const remove_item = obj => _remove_item(obj, item_name);
modify_tree(patterns_by_proto, pattern, remove_item);
}
+#EXPORT pattern_tree_deregister AS deregister
/*
* Yield registered items that match url. Each yielded value is an object with
@@ -281,16 +280,4 @@ function* pattern_tree_search(patterns_by_proto, url)
}
}
}
-
-const pattern_tree = {
- make: () => ({}),
- register: pattern_tree_register,
- deregister: pattern_tree_deregister,
- search: pattern_tree_search
-}
-
-/*
- * EXPORTS_START
- * EXPORT pattern_tree
- * EXPORTS_END
- */
+#EXPORT pattern_tree_search AS search
diff --git a/common/sanitize_JSON.js b/common/sanitize_JSON.js
index c775acb..58519b2 100644
--- a/common/sanitize_JSON.js
+++ b/common/sanitize_JSON.js
@@ -428,8 +428,4 @@ const checks = [
[eq("discard"), i => true, discard, "dummy"]
];
-/*
- * EXPORTS_START
- * EXPORT parse_json_with_schema
- * EXPORTS_END
- */
+#EXPORT parse_json_with_schema
diff --git a/common/settings_query.js b/common/settings_query.js
index 460f265..30e614f 100644
--- a/common/settings_query.js
+++ b/common/settings_query.js
@@ -41,12 +41,9 @@
* proprietary program, I am not going to enforce this in court.
*/
-/*
- * IMPORTS_START
- * IMPORT TYPE_PREFIX
- * IMPORT each_url_pattern
- * IMPORTS_END
- */
+
+#FROM common/stored_types.js IMPORT TYPE_PREFIX
+#FROM common/patterns.js IMPORT each_url_pattern
function query(storage, url, multiple)
{
@@ -65,19 +62,5 @@ function query(storage, url, multiple)
return multiple ? matched : [undefined, undefined];
}
-function query_best(storage, url)
-{
- return query(storage, url, false);
-}
-
-function query_all(storage, url)
-{
- return query(storage, url, true);
-}
-
-/*
- * EXPORTS_START
- * EXPORT query_best
- * EXPORT query_all
- * EXPORTS_END
- */
+#EXPORT (storage, url) => query(storage, url, false) AS query_best
+#EXPORT (storage, url) => query(storage, url, true) AS query_all
diff --git a/common/sha256.js b/common/sha256.js
index 3a02d7a..088473e 100644
--- a/common/sha256.js
+++ b/common/sha256.js
@@ -533,10 +533,4 @@ if (COMMON_JS) {
}
}
-const sha256 = fake_window.sha256;
-
-/*
- * EXPORTS_START
- * EXPORT sha256
- * EXPORTS_END
- */
+#EXPORT fake_window.sha256 AS sha256
diff --git a/common/storage_client.js b/common/storage_client.js
index f310648..4bc3c3c 100644
--- a/common/storage_client.js
+++ b/common/storage_client.js
@@ -41,14 +41,11 @@
* proprietary program, I am not going to enforce this in court.
*/
-/*
- * IMPORTS_START
- * IMPORT CONNECTION_TYPE
- * IMPORT list_prefixes
- * IMPORT make_once
- * IMPORT browser
- * IMPORTS_END
- */
+#IMPORT common/connection_types.js AS CONNECTION_TYPE
+
+#FROM common/browser.js IMPORT browser
+#FROM common/stored_types.js IMPORT list_prefixes
+#FROM common/once.js IMPORT make_once
var call_id = 0;
var port;
@@ -208,10 +205,4 @@ exports.get_all_it = function (prefix)
return list_entries_it(list_by_prefix[prefix]);
}
-const get_remote_storage = make_once(init);
-
-/*
- * EXPORTS_START
- * EXPORT get_remote_storage
- * EXPORTS_END
- */
+#EXPORT make_once(init) AS get_remote_storage
diff --git a/common/storage_light.js b/common/storage_light.js
index a315858..9ec3020 100644
--- a/common/storage_light.js
+++ b/common/storage_light.js
@@ -41,14 +41,10 @@
* proprietary program, I am not going to enforce this in court.
*/
-/*
- * IMPORTS_START
- * IMPORT TYPE_PREFIX
- * IMPORT raw_storage
- * IMPORT is_mozilla
- * IMPORT observables
- * IMPORTS_END
- */
+#IMPORT common/storage_raw.js AS raw_storage
+#IMPORT common/observables.js
+
+#FROM common/stored_types.js IMPORT TYPE_PREFIX
const reg_spec = new Set(["\\", "[", "]", "(", ")", "{", "}", ".", "*", "+"]);
const escape_reg_special = c => reg_spec.has(c) ? "\\" + c : c;
@@ -80,6 +76,7 @@ function listen(callback, prefix, name)
by_name.set(name, name_reg);
}
}
+#EXPORT listen
function no_listen(callback, prefix, name)
{
@@ -103,11 +100,16 @@ function no_listen(callback, prefix, name)
if (by_prefix.size === 0)
listeners_by_callback.delete(callback);
}
+#EXPORT no_listen
function storage_change_callback(changes, area)
{
- if (is_mozilla && area !== "local")
- {console.log("area", area);return;}
+#IF MOZILLA
+ if (area !== "local") {
+ console.warn("change in storage area", area);
+ return;
+ }
+#ENDIF
for (const item of Object.keys(changes)) {
for (const [callback, by_prefix] of listeners_by_callback.entries()) {
@@ -144,22 +146,17 @@ async function observe(prefix, name)
return observable;
}
+#EXPORT observe
-const observe_var = name => observe(TYPE_PREFIX.VAR, name);
+#EXPORT name => observe(TYPE_PREFIX.VAR, name) AS observe_var
function no_observe(observable)
{
no_listen(...created_observables.get(observable) || []);
created_observables.delete(observable);
}
+#EXPORT no_observe
-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
- */
+#EXPORT raw_storage.set AS set
+#EXPORT raw_storage.set_var AS set_var
+#EXPORT raw_storage.get_var AS get_var
diff --git a/common/storage_raw.js b/common/storage_raw.js
index c79fe84..4009f13 100644
--- a/common/storage_raw.js
+++ b/common/storage_raw.js
@@ -41,23 +41,20 @@
* proprietary program, I am not going to enforce this in court.
*/
-/*
- * IMPORTS_START
- * IMPORT TYPE_PREFIX
- * IMPORT browser
- * IMPORT is_chrome
- * IMPORTS_END
- */
+#FROM common/browser.js IMPORT browser
+#FROM common/stored_types.js IMPORT TYPE_PREFIX
async function get(key)
{
- /* Fix for fact that Chrome does not use promises here */
- const promise = is_chrome ?
- new Promise(resolve => chrome.storage.local.get(key, resolve)) :
- browser.storage.local.get(key);
+#IF CHROMIUM
+ const promise = new Promise(cb => browser.storage.local.get(key, cb));
+#ELIF MOZILLA
+ const promise = browser.storage.local.get(key);
+#ENDIF
return (await promise)[key];
}
+#EXPORT get
async function set(key_or_object, value)
{
@@ -65,25 +62,21 @@ async function set(key_or_object, value)
key_or_object : {[key_or_object]: value};
return browser.storage.local.set(arg);
}
+#EXPORT set
async function set_var(name, value)
{
return set(TYPE_PREFIX.VAR + name, value);
}
+#EXPORT set_var
async function get_var(name)
{
return get(TYPE_PREFIX.VAR + name);
}
+#EXPORT get_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
- * EXPORT raw_storage
- * EXPORTS_END
- */
+#EXPORT cb => on_changed.addListener(cb) AS listen
+#EXPORT cb => on_changed.removeListener(cb) AS no_listen
diff --git a/common/stored_types.js b/common/stored_types.js
index 6c69dd7..485969b 100644
--- a/common/stored_types.js
+++ b/common/stored_types.js
@@ -59,6 +59,8 @@ const TYPE_PREFIX = {
URL : "u"
};
+#EXPORT TYPE_PREFIX
+
const TYPE_NAME = {
[TYPE_PREFIX.REPO] : "repo",
[TYPE_PREFIX.PAGE] : "page",
@@ -66,6 +68,8 @@ const TYPE_NAME = {
[TYPE_PREFIX.SCRIPT] : "script"
}
+#EXPORT TYPE_NAME
+
const list_prefixes = [
TYPE_PREFIX.REPO,
TYPE_PREFIX.PAGE,
@@ -73,10 +77,4 @@ const list_prefixes = [
TYPE_PREFIX.SCRIPT
];
-/*
- * EXPORTS_START
- * EXPORT TYPE_PREFIX
- * EXPORT TYPE_NAME
- * EXPORT list_prefixes
- * EXPORTS_END
- */
+#EXPORT list_prefixes
diff --git a/compute_scripts.awk b/compute_scripts.awk
index 3d8a5b0..9edc56d 100644..100755
--- a/compute_scripts.awk
+++ b/compute_scripts.awk
@@ -1,3 +1,5 @@
+#!/usr/bin/awk -f
+#
# SPDX-License-Identifier: CC0-1.0
#
# Process javascript files and resolve dependencies between them
@@ -15,195 +17,683 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# CC0 1.0 Universal License for more details.
-function read_file(filename,
- imports_state, exports_state, line, record, result) {
- imports_state = "not_started"
- exports_state = "not_started"
+BEGIN {
+ true = 1
+ false = 0
+}
+
+BEGIN {
+ identifier_re = "[_a-zA-Z][_a-zA-Z0-9]*"
+ path_dir_re = "([-_a-zA-Z0-9][-._a-zA-Z0-9]*/)*"
+ path_ext_re = "(\\.[-_.a-zA-Z0-9]*)?"
+ path_re = "^" path_dir_re identifier_re path_ext_re "$"
+
+ directive_args_patterns["IF"] = "^(NOT[[:space:]]+)?" identifier_re "$"
+ directive_args_patterns["ENDIF"] = "^$"
+ directive_args_patterns["ELSE"] = "^$"
+ directive_args_patterns["ELIF"] = "^(NOT[[:space:]]+)?" identifier_re "$"
+ directive_args_patterns["ERROR"] = "^.*$"
+ directive_args_patterns["COPY"] = "^[^[:space:]]+$"
+ directive_args_patterns["INCLUDE"] = "^[^[:space:]]+$"
+ directive_args_patterns["INCLUDE_VERBATIM"] = "^[^[:space:]]+$"
+
+ AS_re = "AS[[:space:]]+" identifier_re
+ maybe_AS_re = "([[:space:]]+" AS_re ")?"
+ FROM_clause_re = identifier_re maybe_AS_re
+ more_FROM_clauses_re = "([[:space:]]*,[[:space:]]*" FROM_clause_re ")*"
+ FROM_IMPORT_re = "[^[:space:]]+[[:space:]]+IMPORT[[:space:]]+"
+ EXPORT_AS_re = ".*[^[:space:]][[:space:]]+" AS_re
+
+ directive_args_patterns["IMPORT"] = "^[^[:space:]]+" maybe_AS_re "$"
+ directive_args_patterns["FROM"] = ("^" FROM_IMPORT_re FROM_clause_re \
+ more_FROM_clauses_re "$")
+ directive_args_patterns["EXPORT"] = "^(" EXPORT_AS_re "|" identifier_re ")$"
+
+ directive_args_patterns["LOADJS"] = "^[^[:space:]]+$"
+}
+
+function validate_path(read_path, path, line) {
+ if (path !~ (path_re)) {
+ printf "ERROR: File path in %s does not match '%s': %s\n",
+ read_path, path_re, line > "/dev/stderr"
+ return 1
+ }
+
+ return 0
+}
+
+function identifier_from_path(path) {
+ sub("^" path_dir_re, "", path)
+ sub(path_ext_re "$", "", path)
+
+ return path
+}
+
+function last_token(line) {
+ sub("^.*[[:space:]]", "", line)
+
+ return line
+}
+
+function first_token(line) {
+ sub("[[:space:]].*$", "", line)
+
+ return line
+}
+
+function is_empty(array, key) {
+ for (key in array)
+ return false
+ return true
+}
- do {
- result = (getline line < filename)
+function clear_array(array, key) {
+ for (key in array)
+ delete array[key]
+}
+
+function add_line(path, line, where) {
+ if (where != "amalgamation_root_file")
+ lines[path,++lines_count[path]] = line
+
+ if (where != "non_amalgamation_file" &&
+ path == js_to_amalgamate)
+ main_js_lines[++main_js_lines_count] = line
+}
+
+BEGIN {
+ delete page_js_deps[0]
+ page_js_deps_count = 0
+}
+
+function process_file(path, read_path, mode,
+ line, result, line_part, directive, directive_args,
+ if_nesting, if_nesting_true, if_branch_processed) {
+ if (path in modes && modes[path] != mode) {
+ printf "ERROR: File %s used multiple times in different contexts\n",
+ path > "/dev/stderr"
+ return 1
+ }
+
+ if (mode == "html" && path == read_path) {
+ clear_array(page_js_deps)
+ page_js_deps_count = 0
+ }
+
+ modes[path] = mode
+
+ if (!(path in reading)) {
+ if (path in lines_count)
+ return 0
+ lines_count[path]
+ }
+
+ reading[read_path]
+
+ if (mode == "js" && path == read_path) {
+ add_line(path, "\"use strict\";")
+ add_line(path, "this.haketilo_exports = this.haketilo_exports || {};")
+ add_line(path, "this.haketilo_exports[\"" path "\"] = {};")
+
+ add_line(path, "window.globalThis = this", "amalgamation_root_file")
+
+ add_line(path, "")
+
+ add_line(path, "(function() {", "non_amalgamation_file")
+ add_line(path, "var globalThis = this.haketilo_this",
+ "non_amalgamation_file")
+ add_line(path, "{", "non_amalgamation_file")
+ }
+
+ while (true) {
+ result = (getline line < read_path)
if (result < 0) {
- printf "error reading %s", filename
- exit 1
+ printf "ERROR: Could not read %s\n", read_path > "/dev/stderr"
+ return 1
}
+ if (result == 0)
+ break
- if (imports_state == "started" &&
- line ~ /^([[:space:]]*\*[[:space:]]+)?IMPORT[[:space:]]+[_a-zA-Z][_a-zA-Z0-9]*[[:space:]]*$/) {
- record = line
+ if (line !~ /^#/) {
+ if (if_nesting_true == if_nesting)
+ add_line(path, line)
+ continue
+ }
- sub(/^([[:space:]]*\*[[:space:]]+)?IMPORT[[:space:]]+/, "", record)
- sub(/([[:space:]]+$)/, "", record)
+ while (line ~ /\\$/) {
+ sub(/\\$/, "", line)
- imports[filename,++import_counts[filename]] = record
+ result = (getline line_part < read_path)
+ if (result < 0) {
+ printf "ERROR: Could not read %s\n", read_path > "/dev/stderr"
+ return 1
+ }
+ if (result == 0) {
+ printf "ERROR: Unexpected EOF in %s\n",
+ read_path > "/dev/stderr"
+ return 1
+ }
+
+ line = line " " line_part
+ }
+
+ directive = substr(line, 2)
+ sub(/[[:space:]].*$/, "", directive)
+
+ if (directive !~ \
+ /^(IF|ENDIF|ELSE|ELIF|ERROR|INCLUDE|INCLUDE_VERBATIM|COPY_FILE)$/ &&
+ (mode != "js" || directive !~ /^(IMPORT|FROM|EXPORT)$/) &&
+ (mode != "html" || directive !~ /^LOADJS$/) &&
+ (mode != "manifest" || directive !~ /^(LOADJS|LOADHTML)$/)) {
+ printf "ERROR: Invalid # directive in %s: %s\n",
+ read_path, line > "/dev/stderr"
+ return 1
+ }
+
+ directive_args = line
+ sub(/^#[^[:space:]]*[[:space:]]*/, "", directive_args)
+ sub(/[[:space:]]*$/, "", directive_args)
+
+ if (directive_args !~ directive_args_patterns[directive]) {
+ printf "ERROR: #%s arguments in %s do not match '%s': %s\n",
+ directive, read_path, directive_args_patterns[directive], line \
+ > "/dev/stderr"
+ return 1
}
- if (imports_state == "started" &&
- line ~ /^([[:space:]]*\*[[:space:]]+)?IMPORTS_END[[:space:]]*$/)
- imports_state = "finished"
- if (imports_state == "not_started" &&
- line ~ /^([[:space:]]*\*[[:space:]]+)?IMPORTS_START[[:space:]]*$/)
- imports_state = "started"
-
- if (exports_state == "started" &&
- line ~ /^([[:space:]]*\*[[:space:]]+)?EXPORT[[:space:]]+[_a-zA-Z][_a-zA-Z0-9]*[[:space:]]*$/) {
- record = line
-
- sub(/^([[:space:]]*\*[[:space:]]+)?EXPORT[[:space:]]+/, "", record)
- sub(/([[:space:]]+$)/, "", record)
-
- if (record in exports) {
- printf "ERROR: '%s' exported by both %s and %s\n",
- exports[record], filename > "/dev/stderr"
+
+ if (directive == "IF") {
+ if (if_nesting_true == if_nesting) {
+ if ((last_token(directive_args) in defines) == \
+ (directive_args ~ /^[^[:space:]]+$/))
+ if_nesting_true++
+ else
+ if_branch_processed = false
}
- provides[record] = filename
- exports[filename,++export_counts[filename]] = record
+ if_nesting++
+ } else if (directive == "ENDIF") {
+ if (if_nesting == 0) {
+ printf "ERROR: Spurious #ENDIF in %s\n",
+ read_path > "/dev/stderr"
+ return 1
+ }
+
+ if (if_nesting_true == if_nesting)
+ if_nesting_true--
+
+ if_nesting--
+ } else if (directive == "ELSE") {
+ if (if_nesting == 0) {
+ printf "ERROR: Spurious #ELSE in %s\n",
+ read_path > "/dev/stderr"
+ return 1
+ }
+
+ if (if_nesting == if_nesting_true + 1 && !if_branch_processed) {
+ if_nesting_true++
+ } else if (if_nesting == if_nesting_true) {
+ if_branch_processed = true
+ if_nesting_true--
+ }
+ } else if (directive == "ELIF") {
+ if (if_nesting == 0) {
+ printf "ERROR: Spurious #ELIF in %s\n",
+ read_path > "/dev/stderr"
+ return 1
+ }
+
+ if (if_nesting == if_nesting_true + 1 && !if_branch_processed &&
+ (last_token(directive_args) in defines) == \
+ (directive_args ~ /^[^[:space:]]+$/)) {
+ if_nesting_true++
+ } else if (if_nesting == if_nesting_true) {
+ if_branch_processed = true
+ if_nesting_true--
+ }
+ } else if (if_nesting_true != if_nesting) {
+ continue
+ } else if (directive == "ERROR") {
+ printf "ERROR: File %s says: %s\n",
+ read_path, directive_args > "/dev/stderr"
+ return 1
+ } else if (directive == "INCLUDE") {
+ if (include_file(path, read_path, directive_args, line))
+ return 1
+ } else if (directive == "INCLUDE_VERBATIM") {
+ if (include_file(path, read_path, directive_args, line, true))
+ return 1
+ } else if (directive == "COPY_FILE") {
+ if (mark_copy_file(path, read_path, directive_args, line))
+ return 1
+ } else if (directive == "IMPORT") {
+ if (import_js_file(path, read_path, directive_args, line))
+ return 1
+ } else if (directive == "FROM") {
+ if (import_from_js_file(path, read_path, directive_args, line))
+ return 1
+ } else if (directive == "EXPORT") {
+ if (export_from_js_file(path, read_path, directive_args, line))
+ return 1
+ } else if (directive == "LOADJS") {
+ if (mode == "html") {
+ page_js_deps_count = \
+ load_js_file(path, read_path, directive_args, line,
+ page_js_deps, page_js_deps_count)
+ if (page_js_deps_count < 1)
+ return 1
+ } else if (mode == "manifest") {
+ if (load_js_file(path, read_path, directive_args, line) < 1)
+ return 1
+ }
+ } else if (directive == "LOADHTML") {
+ if (load_html_file(path, read_path, directive_args, line))
+ return 1
}
- if (exports_state == "started" &&
- line ~ /^([[:space:]]*\*[[:space:]]+)?EXPORTS_END[[:space:]]*$/)
- exports_state = "finished"
- if (exports_state == "not_started" &&
- line ~ /^([[:space:]]*\*[[:space:]]+)?EXPORTS_START[[:space:]]*$/)
- exports_state = "started"
- } while (result > 0)
+ }
+
+ close(read_path)
- if (imports_state == "started") {
- printf "ERROR: Unclosed IMPORTS list in '%s'\n", filename \
- > "/dev/stderr"
- exit 1
+ if (if_nesting) {
+ printf "ERROR: Unterminated #IF in %s\n", read_path > "/dev/stderr"
+ return 1
}
- if (exports_state == "started") {
- printf "ERROR: Unclosed EXPORTS list in '%s'\n", filename \
- > "/dev/stderr"
- exit 1
+ if (mode == "js" && path == read_path) {
+ add_line(path, "}", "non_amalgamation_file")
+ add_line(path, "}).call({", "non_amalgamation_file")
+ add_line(path, " haketilo_exports: this.haketilo_exports,",
+ "non_amalgamation_file")
+ add_line(path, " haketilo_this: this",
+ "non_amalgamation_file")
+ add_line(path, "});", "non_amalgamation_file")
}
- close(filename)
+ delete reading[read_path]
}
-function print_file(filename, line) {
- while ((getline line < filename) > 0)
- print(line)
+function include_file(root_path, read_path, included_path, line, verbatim,
+ read_line, result) {
+ if (validate_path(read_path, included_path, line))
+ return 1
+
+ if (included_path in reading) {
+ printf "ERROR: Inclusion loop when including %s in %s\n",
+ included_path, read_path > "/dev/stderr"
+ return 1
+ }
+
+ if (verbatim) {
+ while(true) {
+ result = (getline read_line < included_path)
+ if (result > 0)
+ add_line(root_path, read_line)
+ else
+ break
+ }
+
+ if (result == 0) {
+ close(included_path)
+ return 0
+ }
- close(filename)
+ printf "ERROR: Could not read %s\n", included_path > "/dev/stderr"
+ } else {
+ if (process_file(root_path, included_path, modes[root_path]) == 0)
+ return 0
+ }
+
+ printf " when including %s in %s\n",
+ included_path, read_path > "/dev/stderr"
+
+ return 1
}
-function print_imports_code(filename, declarator, i, count, import_name) {
- count = import_counts[filename]
- for (i = 1; i <= count; i++) {
- import_name = imports[filename,i]
- printf "%s %s = window.haketilo_exports.%s;\n",
- declarator, import_name, import_name
+function mark_copy_file(root_path, read_path, copied_path, line) {
+ if (validate_path(read_path, copied_path, line))
+ return 1
+
+ to_copy[copied_path]
+
+ return 0
+}
+
+function satisfy_import(root_path, imported_path, as, what,
+ added_line, description, count) {
+ if ((root_path,as) in imports_from) {
+ printf "ERROR: Multiple items imported under the name '%s' in %s\n",
+ as, root_path > "/dev/stderr"
+ return 1
}
+
+ added_line = " " as " = haketilo_exports[\"" imported_path "\"]"
+ if (what)
+ added_line = added_line "." what
+
+ add_line(root_path, "const" added_line ";", "non_amalgamation_file")
+ add_line(root_path, "let" added_line ";", "amalgamation_root_file")
+
+ count = ++import_counts[root_path]
+
+ imports_as [root_path,count] = as
+ imports_from[root_path,as] = imported_path
+ imports_what[root_path,as] = what
+
+ if (what)
+ description = "'" what "' from " imported_path
+ else
+ description = imported_path
+
+ description = description " needed by " root_path
+
+ if (imported_path in reading) {
+ printf "ERROR: dependency loop when importing %s\n",
+ description > "/dev/stderr"
+ return 1
+ } else if (process_file(imported_path, imported_path, "js")) {
+ printf " when importing %s\n", description > "/dev/stderr"
+ return 1
+ }
+
+ if (what && !((imported_path,what) in exports)) {
+ printf "ERROR: %s doesn't export '%s' needed by %s\n",
+ imported_path, what, root_path > "/dev/stderr"
+ return 1
+ }
+
+ return 0
}
-function print_exports_code(filename, i, count, export_name) {
- count = export_counts[filename]
- for (i = 1; i <= count; i++) {
- export_name = exports[filename,i]
- printf "window.haketilo_exports.%s = %s;\n", export_name, export_name
+function import_js_file(root_path, read_path, directive_args, line,
+ imported_path, as) {
+ imported_path = first_token(directive_args)
+ if (validate_path(read_path, imported_path, line))
+ return 1
+
+ if (line ~ (AS_re "$"))
+ as = last_token(directive_args)
+ else
+ as = identifier_from_path(imported_path)
+
+ return satisfy_import(root_path, imported_path, as)
+}
+
+function import_from_js_file(root_path, read_path, directive_args, line,
+ imported_path, args_copy, FROM_clause, as) {
+ imported_path = first_token(directive_args)
+ if (validate_path(read_path, imported_path, line))
+ return 1
+
+ args_copy = directive_args
+ sub("^" FROM_IMPORT_re, "", args_copy)
+ args_copy = "," args_copy
+
+ while (args_copy ~ /,/) {
+ sub(/^[^,]*,[[:space:]]*/, "", args_copy)
+
+ FROM_clause = args_copy
+ sub(/[[:space:]]*,.*$/, "", FROM_clause)
+
+ if (satisfy_import(root_path, imported_path,
+ last_token(FROM_clause), first_token(FROM_clause)))
+ return 1
}
+
+ return 0
}
-function partially_wrap_file(filename, declarator) {
- if (!declarator)
- declarator = "const"
+function export_from_js_file(root_path, read_path, directive_args, line,
+ as, exported_item, added_line) {
+ as = last_token(directive_args)
+
+ if (directive_args ~ ("^" identifier_re "$")) {
+ exported_item = as
+ } else {
+ exported_item = directive_args
+ sub("[[:space:]]+" AS_re "$", "", exported_item)
+ }
+
+ if ((root_path,as) in exports) {
+ printf "ERROR: Multiple values exported under the name '%s' in %s\n",
+ as, root_path > "/dev/stderr"
+ return 1
+ }
- print_imports_code(filename, declarator)
- printf "\n\n"
+ added_line = \
+ "this.haketilo_exports[\"" root_path "\"]." as " = (" exported_item ");"
+ add_line(root_path, added_line)
- print_file(filename)
+ exports[root_path,as]
- printf "\n\n"
- print_exports_code(filename)
+ return 0
}
-function wrap_file(filename) {
- print "\"use strict\";\n\n({fun: (function() {\n"
+function compute_deps(js_path, dependencies, count, dependencies_added,
+ i_max, i, as, next_path) {
+ delete dependencies_added[0]
- partially_wrap_file(filename)
+ if (process_file(js_path, js_path, "js"))
+ return 0
- print "\n})}).fun();"
+ i_max = import_counts[js_path]
+ for (i = 1; i <= i_max; i++) {
+ as = imports_as[js_path,i]
+ next_path = imports_from[js_path,as]
+ if (next_path in dependencies_added)
+ continue
+
+ count = compute_deps(next_path, dependencies, count, dependencies_added)
+ if (count < 1)
+ return 0
+ }
+
+ dependencies_added[js_path]
+ dependencies[++count] = js_path
+
+ return count
}
-function compute_dependencies(filename, i, count, import_name, next_file) {
- if (processed[filename] == "used")
+# Here js_deps and js_deps_count are optional args, used when loading scripts
+# into an HTML page to avoid having th same script loaded twice in multiple
+# places.
+function load_js_file(root_path, read_path, loaded_path, line,
+ js_deps, js_deps_count,
+ js_deps_already_added, i, added_line) {
+ delete js_deps[""]
+ delete js_deps_already_added[0]
+
+ if (validate_path(read_path, loaded_path, line))
return 0
- if (processed[filename] == "on_stack") {
- printf "import loop on %s\n", filename > "/dev/stderr"
+ for (i = 1; i <= js_deps_count; i++)
+ js_deps_already_added[js_deps[i]]
+
+ i = js_deps_count
+
+ js_deps_count = compute_deps(loaded_path, js_deps,
+ js_deps_count, js_deps_already_added)
+
+ if (js_deps_count < 1) {
+ printf " when loading %s from %s\n",
+ loaded_path, read_path > "/dev/stderr"
+ return 0
+ }
+
+ while (++i <= js_deps_count) {
+ if (modes[root_path] == "html") {
+ added_line = "<script src=\"/" js_deps[i] "\"></script>"
+ } else { #if (modes[root_path] == "manifest") {
+ added_line = "\"" js_deps[i] "\""
+ if (i != js_deps_count)
+ added_line = added_line ","
+ }
+ add_line(root_path, added_line)
+ }
+
+ return js_deps_count
+}
+
+function load_html_file(root_path, read_path, loaded_path, line) {
+ if (validate_path(read_path, loaded_path, line))
+ return 1
+
+ if (process_file(loaded_path, loaded_path, "html")) {
+ printf " when loading %s from %s\n",
+ loaded_path, read_path, line > "/dev/stderr"
return 1
}
- processed[filename] = "on_stack"
+ return 0
+}
- count = import_counts[filename]
- for (i = 1; i <= count; i++) {
- import_name = imports[filename,i]
- if (!(import_name in provides)) {
- printf "nothing exports %s, required by %s\n",
- import_name, filename > "/dev/stderr"
- return 1
- }
+function print_amalgamation(js_deps, js_deps_count,
+ js_dep_nr, path, max_line_nr, line_nr) {
+ delete js_deps[0]
- if (compute_dependencies(provides[import_name]) > 0) {
- printf "when satisfying %s for %s\n",
- import_name, filename > "/dev/stderr"
- return 1
- }
+ js_deps_count = compute_deps(js_to_amalgamate, js_deps, 0)
+ if (js_deps_count < 1)
+ return 1
+
+ # '<' instead of '<=' because we print the main js file below instead
+ for (js_dep_nr = 1; js_dep_nr < js_deps_count; js_dep_nr++) {
+ path = js_deps[js_dep_nr]
+ max_line_nr = lines_count[path]
+
+ for (line_nr = 1; line_nr <= max_line_nr; line_nr++)
+ print lines[path, line_nr]
}
- processed[filename] = "used"
- print filename
+ for (line_nr = 1; line_nr <= main_js_lines_count; line_nr++)
+ print main_js_lines[line_nr]
return 0
}
function print_usage() {
- printf "usage: %2 compute_scripts.awk script_dependencies|wrapped_code|partially_wrapped_code FILENAME[...]\n",
+ printf "USAGE: %s compute_scripts.awk -- [-D PREPROCESSOR_DEFINITION]... [-M manifest/to/process/manifest.json]... [-H html/to/process.html]... [-J js/to/process.js]... [--help|-h] [--output-dir=./build] [--write-js-deps] [--write-html-deps] [--output=files-to-copy|--output=amalgamate-js:js/to/process.js]\n",
ARGV[0] > "/dev/stderr"
- exit 1
}
-function mock_exports_init() {
- provides["browser"] = "exports_init.js"
- provides["is_chrome"] = "exports_init.js"
- provides["is_mozilla"] = "exports_init.js"
- provides["initial_data"] = "exports_init.js"
-
- processed["exports_init.js"] = "used"
+BEGIN {
+ option_arg_patterns["D"] = "^" identifier_re "$"
+ option_arg_patterns["M"] = path_re
+ option_arg_patterns["H"] = path_re
+ option_arg_patterns["J"] = path_re
}
-BEGIN {
- operation = ARGV[1]
+function main(i, path, letter, dir, max_line_nr, js_deps, js_deps_count) {
+ output_dir = "./build"
+ write_js_deps = false
+ write_html_deps = false
- if (ARGC < 3)
- print_usage()
+ output = ""
+ js_to_amalgamate = ""
+ delete main_js_lines[0]
- root_filename = ARGV[2]
+ delete manifests_to_process[0]
+ delete html_to_process[0]
+ delete js_to_process[0]
- for (i = 2; i < ARGC; i++)
- filenames[ARGV[i]]
+ delete explicitly_requested[0]
- mock_exports_init()
+ for (i = 1; i < ARGC; i++) {
+ if (ARGV[i] ~ /^-[DMHJ]$/) {
+ letter = substr(ARGV[i++], 2)
+ if (i == ARGC || ARGV[i] !~ option_arg_patterns[letter]) {
+ printf "ERROR: '-%s' option should be followed by an argument matching '%s'\n",
+ letter, option_arg_patterns[letter] > "/dev/stderr"
+ return 1
+ }
- for (filename in filenames) {
- # A filename is allowed to appear multiple times in the list.
- # Let's only process it once.
- if (!(filename in processed))
- read_file(filename)
- processed[filename] = "not_used"
+ if (letter == "D")
+ defines[ARGV[i]]
+ else
+ explicitly_requested[ARGV[i]]
+
+ if (letter == "M")
+ manifests_to_process[ARGV[i]]
+ if (letter == "H")
+ html_to_process[ARGV[i]]
+ if (letter == "J")
+ js_to_process[ARGV[i]]
+ } else if (ARGV[i] ~ /^-(-help|h)$/ ) {
+ print_usage()
+ return 0
+ } else if (ARGV[i] ~ /^--output-dir=/) {
+ output_dir = ARGV[i]
+ sub(/^--output-dir=/, "", output_dir)
+ } else if (ARGV[i] ~ /^--write-js-deps$/) {
+ write_js_deps = true
+ } else if (ARGV[i] ~ /^--write-html-deps$/) {
+ write_html_deps = true
+ } else if (ARGV[i] ~ /^--output=files-to-copy$/) {
+ output = "files-to-copy"
+ } else if (ARGV[i] ~ /^--output=amalgamate-js:/) {
+ output = "amalgamate-js"
+ js_to_amalgamate = ARGV[i]
+ sub(/^--output=amalgamate-js:/, "", js_to_amalgamate)
+ if (js_to_amalgamate !~ path_re) {
+ printf "ERROR: amalgamate-js path does not match '%s': %s\n",
+ path_re, js_to_amalgamate > "/dev/stderr"
+ return 1
+ }
+ } else {
+ printf "ERROR: Unknown option '%s'\n", ARGV[i] > "/dev/stderr"
+ print_usage()
+ return 1
+ }
}
- if (operation == "script_dependencies") {
- print("exports_init.js")
- if (compute_dependencies(root_filename) > 0)
- exit 1
- } else if (operation == "partially_wrapped_code") {
- partially_wrap_file(root_filename, "let")
- } else if (operation == "wrapped_code") {
- wrap_file(root_filename)
- } else {
- print_usage()
+ if (is_empty(explicitly_requested) && output != "amalgamate-js") {
+ explicitly_requested["manifest.json"]
+ manifests_to_process["manifest.json"]
}
+
+ for (path in manifests_to_process) {
+ if (process_file(path, path, "manifest"))
+ return 1
+ }
+ for (path in html_to_process) {
+ if (process_file(path, path, "html"))
+ return 1
+ }
+ for (path in js_to_process) {
+ if (process_file(path, path, "js"))
+ return 1
+ }
+
+ for (path in lines_count) {
+ if (!(path in explicitly_requested) &&
+ !(modes[path] == "js" && write_js_deps) &&
+ !(modes[path] == "html" && write_html_deps))
+ continue
+
+ dir = path
+ sub(/[^/]*$/, "", dir)
+ dir = output_dir "/" dir
+ sub("'", "'\\''", dir)
+
+ system("mkdir -p '" dir "'")
+
+ printf "" > (output_dir "/" path)
+
+ max_line_nr = lines_count[path]
+ for (i = 1; i <= max_line_nr; i++)
+ print lines[path, i] >> (output_dir "/" path)
+ }
+
+ if (output == "files-to-copy") {
+ for (path in to_copy)
+ print path
+ }
+
+ if (output == "amalgamate-js") {
+ if (print_amalgamation())
+ return 1
+ }
+
+ return 0
+}
+
+BEGIN {
+ exit main()
}
diff --git a/content/activity_info_server.js b/content/activity_info_server.js
index c1b9736..db8ff80 100644
--- a/content/activity_info_server.js
+++ b/content/activity_info_server.js
@@ -42,15 +42,10 @@
* proprietary program, I am not going to enforce this in court.
*/
-/*
- * IMPORTS_START
- * IMPORT listen_for_connection
- * IMPORT CONNECTION_TYPE
- * IMPORT repo_query
- * IMPORT subscribe_repo_query_results
- * IMPORT unsubscribe_repo_query_results
- * IMPORTS_END
- */
+#IMPORT common/connection_types.js AS CONNECTION_TYPE
+#IMPORT content/repo_query.js
+
+#FROM common/message_server.js IMPORT listen_for_connection
var activities = [];
var ports = new Set();
@@ -73,6 +68,7 @@ function report_script(script_data)
{
report_activity("script", script_data);
}
+#EXPORT report_script
function report_settings(settings)
{
@@ -80,11 +76,13 @@ function report_settings(settings)
Object.assign(settings_clone, settings)
report_activity("settings", settings_clone);
}
+#EXPORT report_settings
function report_document_type(is_html)
{
report_activity("is_html", is_html);
}
+#EXPORT report_document_type
function report_repo_query_action(update, port)
{
@@ -93,13 +91,13 @@ function report_repo_query_action(update, port)
function trigger_repo_query(query_specifier)
{
- repo_query(...query_specifier);
+ repo_query.query(...query_specifier);
}
function handle_disconnect(port, report_action)
{
ports.delete(port)
- unsubscribe_repo_query_results(report_action);
+ repo_query.unsubscribe_results(report_action);
}
function new_connection(port)
@@ -112,7 +110,7 @@ function new_connection(port)
port.postMessage(activity);
const report_action = u => report_repo_query_action(u, port);
- subscribe_repo_query_results(report_action);
+ repo_query.subscribe_results(report_action);
/*
* So far the only thing we expect to receive is repo query order. Once more
@@ -123,16 +121,8 @@ function new_connection(port)
port.onDisconnect.addListener(() => handle_disconnect(port, report_action));
}
-function start_activity_info_server()
+function start()
{
listen_for_connection(CONNECTION_TYPE.ACTIVITY_INFO, new_connection);
}
-
-/*
- * EXPORTS_START
- * EXPORT start_activity_info_server
- * EXPORT report_script
- * EXPORT report_settings
- * EXPORT report_document_type
- * EXPORTS_END
- */
+#EXPORT start
diff --git a/content/main.js b/content/main.js
index 5a798e0..9e98635 100644
--- a/content/main.js
+++ b/content/main.js
@@ -42,20 +42,12 @@
* proprietary program, I am not going to enforce this in court.
*/
-/*
- * IMPORTS_START
- * IMPORT handle_page_actions
- * IMPORT gen_nonce
- * IMPORT is_privileged_url
- * IMPORT browser
- * IMPORT is_chrome
- * IMPORT is_mozilla
- * IMPORT start_activity_info_server
- * IMPORT make_csp_rule
- * IMPORT csp_header_regex
- * IMPORT report_settings
- * IMPORTS_END
- */
+#IMPORT content/activity_info_server.js
+
+#FROM content/page_actions.js IMPORT handle_page_actions
+#FROM common/misc.js IMPORT gen_nonce, is_privileged_url, \
+ make_csp_rule, csp_header_regex
+#FROM common/browser.js IMPORT browser
document.content_loaded = document.readyState === "complete";
const wait_loaded = e => e.content_loaded ? Promise.resolve() :
@@ -234,12 +226,13 @@ function mozilla_initial_block(doc)
*/
async function sanitize_document(doc, policy)
{
+#IF MOZILLA
/*
* Blocking of scripts that are in the DOM from the beginning. Needed for
* Mozilla.
*/
- if (is_mozilla)
- mozilla_initial_block(doc);
+ mozilla_initial_block(doc);
+#ENDIF
/*
* Ensure our CSP rules are employed from the beginning. This CSP injection
@@ -338,7 +331,7 @@ if (!is_privileged_url(document.URL)) {
console.debug("current policy", policy);
- report_settings(policy);
+ activity_info_server.report_settings(policy);
policy.nonce = gen_nonce();
@@ -350,5 +343,5 @@ if (!is_privileged_url(document.URL)) {
handle_page_actions(policy, doc_ready);
- start_activity_info_server();
+ activity_info_server.start();
}
diff --git a/content/page_actions.js b/content/page_actions.js
index f26e247..b2cc5ce 100644
--- a/content/page_actions.js
+++ b/content/page_actions.js
@@ -41,14 +41,10 @@
* proprietary program, I am not going to enforce this in court.
*/
-/*
- * IMPORTS_START
- * IMPORT CONNECTION_TYPE
- * IMPORT browser
- * IMPORT report_script
- * IMPORT report_document_type
- * IMPORTS_END
- */
+#IMPORT common/connection_types.js AS CONNECTION_TYPE
+
+#FROM common/browser.js IMPORT browser
+#FROM content/activity_info_server.js IMPORT report_script, report_document_type
let policy;
/* Snapshot url and content type early; these can be changed by other code. */
@@ -114,9 +110,4 @@ function handle_page_actions(_policy, doc_ready_promise) {
port.postMessage({payload: policy.payload});
}
}
-
-/*
- * EXPORTS_START
- * EXPORT handle_page_actions
- * EXPORTS_END
- */
+#EXPORT handle_page_actions
diff --git a/content/repo_query.js b/content/repo_query.js
index 3201159..5dd503d 100644
--- a/content/repo_query.js
+++ b/content/repo_query.js
@@ -41,15 +41,12 @@
* proprietary program, I am not going to enforce this in court.
*/
-/*
- * IMPORTS_START
- * IMPORT make_ajax_request
- * IMPORT observables
- * IMPORT TYPE_PREFIX
- * IMPORT parse_json_with_schema
- * IMPORT matchers
- * IMPORTS_END
- */
+#IMPORT common/observables.js
+
+#FROM common/ajax.js IMPORT make_ajax_request
+#FROM common/stored_types.js IMPORT TYPE_PREFIX
+#FROM common/sanitize_JSON.js IMPORT parse_json_with_schema
+#FROM common/misc.js IMPORT matchers
const paths = {
[TYPE_PREFIX.PAGE]: "/pattern",
@@ -71,6 +68,7 @@ function repo_query(prefix, item, repo_urls)
for (const repo_url of repo_urls)
perform_query_against(key, repo_url, results);
}
+#EXPORT repo_query AS query
const page_schema = {
pattern: matchers.nonempty_string,
@@ -127,22 +125,16 @@ async function perform_query_against(key, repo_url, results)
observables.broadcast(observable, broadcast_msg);
}
-function subscribe_repo_query_results(cb)
+function subscribe_results(cb)
{
observables.subscribe(observable, cb);
for (const [key, results] of queried_items.entries())
cb({prefix: key[0], item: key.substring(1), results});
}
+#EXPORT subscribe_results
-function unsubscribe_repo_query_results(cb)
+function unsubscribe_results(cb)
{
observables.unsubscribe(observable, cb);
}
-
-/*
- * EXPORTS_START
- * EXPORT repo_query
- * EXPORT subscribe_repo_query_results
- * EXPORT unsubscribe_repo_query_results
- * EXPORTS_END
- */
+#EXPORT unsubscribe_results
diff --git a/copyright b/copyright
index a2e09d1..08e1358 100644
--- a/copyright
+++ b/copyright
@@ -9,7 +9,7 @@ Comment: Wojtek Kosior promises not to sue even in case of violations
of the license.
Files: *.sh default_settings.json Makefile.in compute_scripts.awk
- CHROMIUM_exports_init.js pytest.ini
+ common/browser.js pytest.ini README.md copyright
Copyright: 2021 Wojtek Kosior <koszko@koszko.org>
License: CC0
@@ -38,25 +38,12 @@ Comment: Code by Wojtek is licensed under GPL-3+-javascript and
Wojtek Kosior promises not to sue even in case of violations of the
license.
-Files: *.html README.txt copyright
+Files: html/*.html html/*.css
Copyright: 2021 Wojtek Kosior <koszko@koszko.org>
License: GPL-3+ or CC-BY-SA-4.0
Comment: Wojtek Kosior promises not to sue even in case of violations
of the license.
-Files: html/*.css
-Copyright: 2021 Wojtek Kosior <koszko@koszko.org>
-License: GPL-3+ or CC-BY-SA-4.0
-Comment: Wojtek Kosior promises not to sue even in case of violations
- of the license.
-
-Files: html/base.css
-Copyright: 2021 Wojtek Kosior <koszko@koszko.org>
- 2021 Nicholas Johnson <nicholasjohnson@posteo.org>
-License: GPL-3+ or CC-BY-SA-4.0
-Comment: Wojtek Kosior promises not to sue even in case of violations
- of the license.
-
Files: html/reset.css
Copyright: 2008,2011 Eric A. Meyer
License: public-domain
diff --git a/html/DOM_helpers.js b/html/DOM_helpers.js
index aaf5621..443e4eb 100644
--- a/html/DOM_helpers.js
+++ b/html/DOM_helpers.js
@@ -41,10 +41,7 @@
* proprietary program, I am not going to enforce this in court.
*/
-function by_id(id)
-{
- return document.getElementById(id);
-}
+#EXPORT id => document.getElementById(id) AS by_id
const known_templates = new Map();
@@ -63,6 +60,7 @@ function get_template(template_id)
known_templates.set(template_id, template);
return template;
}
+#EXPORT get_template
function clone_template(template_id)
{
@@ -86,11 +84,4 @@ function clone_template(template_id)
return result_object;
}
-
-/*
- * EXPORTS_START
- * EXPORT by_id
- * EXPORT get_template
- * EXPORT clone_template
- * EXPORTS_END
- */
+#EXPORT clone_template
diff --git a/html/MOZILLA_scrollbar_fix.css b/html/MOZILLA_scrollbar_fix.css
deleted file mode 100644
index cdbd5c6..0000000
--- a/html/MOZILLA_scrollbar_fix.css
+++ /dev/null
@@ -1,48 +0,0 @@
-/**
- * This file is part of Haketilo.
- *
- * Function: 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/back_button.css b/html/back_button.css
index b83e834..94b18cf 100644
--- a/html/back_button.css
+++ b/html/back_button.css
@@ -1,10 +1,30 @@
-/**
+/*
+ * SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0
+ *
+ * Style for a "back" button with a CSS arrow image
+ *
* This file is part of Haketilo.
*
- * Function: Style for a "back" button with a CSS arrow image.
+ * Copyright (C) 2021 Wojtek Kosior <koszko@koszko.org>
+ *
+ * File is dual-licensed. You can choose either GPLv3+, CC BY-SA or both.
+ *
+ * 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/>.
*
- * Copyright (C) 2021 Wojtek Kosior
- * Redistribution terms are gathered in the `copyright' file.
+ * I, Wojtek Kosior, thereby promise not to sue for violation of this file's
+ * licenses. Although I request that you do not make use this code in a
+ * proprietary program, I am not going to enforce this in court.
*/
.back_button {
diff --git a/html/base.css b/html/base.css
index 517a5c1..47fc66b 100644
--- a/html/base.css
+++ b/html/base.css
@@ -1,11 +1,30 @@
-/**
+/*
+ * SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0
+ *
+ * Base styling common for all Haketilo internal HTML pages
+ *
* This file is part of Haketilo.
*
- * Function: Base styles.
+ * Copyright (C) 2021 Wojtek Kosior <koszko@koszko.org>
+ *
+ * File is dual-licensed. You can choose either GPLv3+, CC BY-SA or both.
+ *
+ * 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/>.
*
- * Copyright (C) 2021 Wojtek Kosior
- * Copyright (C) 2021 Nicholas Johnson
- * Redistribution terms are gathered in the `copyright' file.
+ * I, Wojtek Kosior, thereby promise not to sue for violation of this file's
+ * licenses. Although I request that you do not make use this code in a
+ * proprietary program, I am not going to enforce this in court.
*/
body {
diff --git a/html/default_blocking_policy.html b/html/default_blocking_policy.html
index 50c19ca..547f756 100644
--- a/html/default_blocking_policy.html
+++ b/html/default_blocking_policy.html
@@ -1,10 +1,37 @@
<!--
- Copyright (C) 2021 Wojtek Kosior
- Redistribution terms are gathered in the `copyright' file.
+ SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0
+ Catch-all script-blocking policy setting dialog
+
+ This file is part of Haketilo.
+
+ Copyright (C) 2021 Wojtek Kosior <koszko@koszko.org>
+
+ File is dual-licensed. You can choose either GPLv3+, CC BY-SA or both.
+
+ 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/>.
+
+ I, Wojtek Kosior, thereby promise not to sue for violation of this file's
+ licenses. Although I request that you do not make use this code in a
+ proprietary program, I am not going to enforce this in court.
+ -->
+
+<!--
This is not a standalone page. This file is meant to be imported into other
HTML code.
-->
+
<style>
#blocking_policy_div {
line-height: 2em;
@@ -16,3 +43,4 @@
their own scripts.
<button id="toggle_policy_but">Toggle policy</button>
</span>
+#LOADJS html/default_blocking_policy.js
diff --git a/html/default_blocking_policy.js b/html/default_blocking_policy.js
index 8178386..47435eb 100644
--- a/html/default_blocking_policy.js
+++ b/html/default_blocking_policy.js
@@ -41,13 +41,10 @@
* proprietary program, I am not going to enforce this in court.
*/
-/*
- * IMPORTS_START
- * IMPORT by_id
- * IMPORT light_storage
- * IMPORT observables
- * IMPORTS_END
- */
+#IMPORT common/storage_light.js AS light_storage
+#IMPORT common/observables.js
+
+#FROM html/DOM_helpers.js IMPORT by_id
/*
* Used with `default_blocking_policy.html' to allow user to choose whether to
@@ -75,8 +72,4 @@ async function init_default_policy_dialog()
blocking_policy_span.classList.remove("hide");
}
-/*
- * EXPORTS_START
- * EXPORT init_default_policy_dialog
- * EXPORTS_END
- */
+init_default_policy_dialog();
diff --git a/html/display-panel.html b/html/display_panel.html
index ee9e767..5ee20f1 100644
--- a/html/display-panel.html
+++ b/html/display_panel.html
@@ -1,21 +1,48 @@
<!doctype html>
<!--
+ SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0
+
+ Extension's popup page
+
This file is part of Haketilo.
- Function: Extension's popup page.
+ Copyright (C) 2021 Wojtek Kosior <koszko@koszko.org>
+
+ File is dual-licensed. You can choose either GPLv3+, CC BY-SA or both.
+
+ 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/>.
- Copyright (C) 2021 Wojtek Kosior
- Redistribution terms are gathered in the `copyright' file.
+ I, Wojtek Kosior, thereby promise not to sue for violation of this file's
+ licenses. Although I request that you do not make use this code in a
+ proprietary program, I am not going to enforce this in court.
-->
<html>
<head>
<meta charset="utf-8"/>
<title>Haketilo - page settings</title>
+#COPY_FILE html/reset.css
<link type="text/css" rel="stylesheet" href="reset.css" />
+#COPY_FILE html/base.css
<link type="text/css" rel="stylesheet" href="base.css" />
+#COPY_FILE html/back_button.css
<link type="text/css" rel="stylesheet" href="back_button.css" />
+#COPY_FILE html/table.css
<link type="text/css" rel="stylesheet" href="table.css" />
- <link type="text/css" rel="stylesheet" href="MOZILLA_scrollbar_fix.css" />
+#IF MOZILLA
+#COPY_FILE html/mozilla_scrollbar_fix.css
+ <link type="text/css" rel="stylesheet" href="mozilla_scrollbar_fix.css" />
+#ENDIF
<style>
body {
width: max-content;
@@ -228,7 +255,7 @@
<div id="install_view">
<div class="top has_bottom_line"><h2> Site modifiers install </h2></div>
<div class="padding_inline">
- <IMPORT html/import_frame.html />
+#INCLUDE html/import_frame.html
</div>
</div>
@@ -258,7 +285,7 @@
</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 />
+#INCLUDE html/default_blocking_policy.html
</span>
</div>
</div>
@@ -342,6 +369,7 @@
<div class="has_upper_line"></div>
- <label for="show_main_view_radio" class="back_button"><div></div></label>_POPUPSCRIPTS_
+ <label for="show_main_view_radio" class="back_button"><div></div></label>
+#LOADJS html/display_panel.js
</body>
</html>
diff --git a/html/display-panel.js b/html/display_panel.js
index d48d254..9d880ca 100644
--- a/html/display-panel.js
+++ b/html/display_panel.js
@@ -41,38 +41,32 @@
* proprietary program, I am not going to enforce this in court.
*/
+#IMPORT common/connection_types.js AS CONNECTION_TYPE
+
+#FROM common/browser.js IMPORT browser
/*
- * IMPORTS_START
- * IMPORT browser
- * IMPORT is_chrome
- * IMPORT is_mozilla
- *** Using remote storage here seems inefficient, we only resort to that
- *** 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
- * IMPORT TYPE_PREFIX
- * IMPORT nice_name
- * IMPORT open_in_settings
- * IMPORT each_url_pattern
- * IMPORT by_id
- * IMPORT clone_template
- * IMPORTS_END
+ * Using remote storage here seems inefficient, we only resort to that
+ * temporarily, before all storage access gets reworked.
*/
+#FROM common/storage_client.js IMPORT get_remote_storage
+#FROM html/import_frame.js IMPORT get_import_frame
+#FROM common/settings_query.js IMPORT query_all
+#FROM common/misc.js IMPORT is_privileged_url, nice_name, \
+ open_in_settings
+#FROM common/stored_types.js IMPORT TYPE_PREFIX
+#FROM common/patterns.js IMPORT each_url_pattern
+#FROM html/DOM_helpers.js IMPORT by_id, clone_template
let storage;
let tab_url;
+#IF MOZILLA
/* Force popup <html>'s reflow on stupid Firefox. */
-if (is_mozilla) {
- const reflow_forcer =
- () => document.documentElement.style.width = "-moz-fit-content";
- for (const radio of document.querySelectorAll('[name="current_view"]'))
- radio.addEventListener("change", reflow_forcer);
-}
+const reflow_forcer =
+ () => document.documentElement.style.width = "-moz-fit-content";
+for (const radio of document.querySelectorAll('[name="current_view"]'))
+ radio.addEventListener("change", reflow_forcer);
+#ENDIF
const show_queried_view_radio = by_id("show_queried_view_radio");
@@ -80,11 +74,12 @@ const tab_query = {currentWindow: true, active: true};
async function get_current_tab()
{
- /* Fix for fact that Chrome does not use promises here */
- const promise = is_chrome ?
- new Promise((resolve, reject) =>
- browser.tabs.query(tab_query, tab => resolve(tab))) :
- browser.tabs.query(tab_query);
+#IF CHROMIUM
+ const callback = (cb) => browser.tabs.query(tab_query, tab => cb(tab));
+ const promise = new Promise(callback);
+#ELIF MOZILLA
+ const promise = browser.tabs.query(tab_query);
+#ENDIF
try {
return (await promise)[0];
@@ -194,8 +189,9 @@ function try_to_connect(tab_id)
query_pattern_but.addEventListener("click", start_querying_repos);
- if (is_mozilla)
- setTimeout(() => monitor_connecting(tab_id), 1000);
+#IF MOZILLA
+ setTimeout(() => monitor_connecting(tab_id), 1000);
+#ENDIF
}
function start_querying_repos()
@@ -214,8 +210,10 @@ function handle_disconnect(tab_id, button_cb)
query_pattern_but.removeEventListener("click", button_cb);
content_script_port = null;
- if (is_chrome && !browser.runtime.lastError)
+#IF CHROMIUM
+ if (!browser.runtime.lastError)
return;
+#ENDIF
/* return if error was not during connection initialization */
if (connected_chbx.checked)
@@ -268,7 +266,7 @@ function handle_activity_report(message)
blocked_span.textContent = settings.allow ? "no" : "yes";
if (settings.pattern) {
- pattern_span.textContent = pattern;
+ pattern_span.textContent = settings.pattern;
const settings_opener =
() => open_in_settings(TYPE_PREFIX.PAGE, settings.pattern);
view_pattern_but.classList.remove("hide");
@@ -579,8 +577,6 @@ 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 754b289..5b1f875 100644
--- a/html/import_frame.html
+++ b/html/import_frame.html
@@ -1,10 +1,37 @@
<!--
- Copyright (C) 2021 Wojtek Kosior
- Redistribution terms are gathered in the `copyright' file.
+ SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0
+ Scripts import dialog
+
+ This file is part of Haketilo.
+
+ Copyright (C) 2021 Wojtek Kosior <koszko@koszko.org>
+
+ File is dual-licensed. You can choose either GPLv3+, CC BY-SA or both.
+
+ 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/>.
+
+ I, Wojtek Kosior, thereby promise not to sue for violation of this file's
+ licenses. Although I request that you do not make use this code in a
+ proprietary program, I am not going to enforce this in court.
+ -->
+
+<!--
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/import_frame.js b/html/import_frame.js
index 3d91557..f23f4ea 100644
--- a/html/import_frame.js
+++ b/html/import_frame.js
@@ -41,15 +41,10 @@
* proprietary program, I am not going to enforce this in court.
*/
-/*
- * IMPORTS_START
- * IMPORT get_remote_storage
- * IMPORT by_id
- * IMPORT clone_template
- * IMPORT nice_name
- * IMPORT make_once
- * IMPORTS_END
- */
+#FROM common/storage_client.js IMPORT get_remote_storage
+#FROM html/DOM_helpers.js IMPORT by_id, clone_template
+#FROM common/misc.js IMPORT nice_name
+#FROM common/once.js IMPORT make_once
let storage;
@@ -187,10 +182,4 @@ async function init()
return exports;
}
-const get_import_frame = make_once(init);
-
-/*
- * EXPORTS_START
- * EXPORT get_import_frame
- * EXPORTS_END
- */
+#EXPORT make_once(init) AS get_import_frame
diff --git a/html/mozilla_scrollbar_fix.css b/html/mozilla_scrollbar_fix.css
new file mode 100644
index 0000000..bf74305
--- /dev/null
+++ b/html/mozilla_scrollbar_fix.css
@@ -0,0 +1,67 @@
+/*
+ * SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0
+ *
+ * Hacky fix for vertical scrollbar width being included in child's width
+ *
+ * This file is part of Haketilo.
+ *
+ * Copyright (C) 2021 Wojtek Kosior <koszko@koszko.org>
+ *
+ * File is dual-licensed. You can choose either GPLv3+, CC BY-SA or both.
+ *
+ * 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/>.
+ *
+ * I, Wojtek Kosior, thereby promise not to sue for violation of this file's
+ * licenses. Although I request that you do not make use this code in a
+ * proprietary program, I am not going to enforce this in court.
+ */
+
+/*
+ * 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/options.html b/html/options.html
index 2e8317c..14b7d44 100644
--- a/html/options.html
+++ b/html/options.html
@@ -1,18 +1,41 @@
-<!doctype html>
+<!DOCTYPE html>
<!--
+ SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0
+
+ Extension's settings page
+
This file is part of Haketilo.
- Function: Extension's settings page.
+ Copyright (C) 2021 Wojtek Kosior <koszko@koszko.org>
+
+ File is dual-licensed. You can choose either GPLv3+, CC BY-SA or both.
+
+ 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/>.
- Copyright (C) 2021 Wojtek Kosior
- Redistribution terms are gathered in the `copyright' file.
+ I, Wojtek Kosior, thereby promise not to sue for violation of this file's
+ licenses. Although I request that you do not make use this code in a
+ proprietary program, I am not going to enforce this in court.
-->
<html>
<head>
<meta charset="utf-8"/>
<title>Haketilo options</title>
+#COPY_FILE html/reset.css
<link type="text/css" rel="stylesheet" href="reset.css" />
+#COPY_FILE html/base.css
<link type="text/css" rel="stylesheet" href="base.css" />
+#COPY_FILE html/table.css
<link type="text/css" rel="stylesheet" href="table.css" />
<style>
body {
@@ -272,7 +295,7 @@
</div>
<button id="add_page_but" type="button"> Add page </button>
<br/>
- <IMPORT html/default_blocking_policy.html />
+#INCLUDE html/default_blocking_policy.html
</div>
<div id="bags" class="tab">
<div class="table_wrapper tight_table has_bottom_line has_upper_line">
@@ -383,13 +406,14 @@
<div id="import_window" class="hide popup" position="absolute">
<div class="popup_frame">
<h2> Settings import </h2>
- <IMPORT html/import_frame.html />
+#INCLUDE html/import_frame.html
</div>
</div>
<a id="file_downloader" class="hide"></a>
<form id="file_opener_form" style="visibility: hidden;">
<input type="file" id="file_opener"></input>
- </form>_OPTIONSSCRIPTS_
+ </form>
+#LOADJS html/options_main.js
</body>
</html>
diff --git a/html/options_main.js b/html/options_main.js
index 3620ad8..f362c6b 100644
--- a/html/options_main.js
+++ b/html/options_main.js
@@ -41,21 +41,13 @@
* proprietary program, I am not going to enforce this in court.
*/
-/*
- * IMPORTS_START
- * IMPORT get_remote_storage
- * IMPORT TYPE_PREFIX
- * IMPORT TYPE_NAME
- * IMPORT list_prefixes
- * IMPORT nice_name
- * IMPORT parse_json_with_schema
- * IMPORT get_template
- * IMPORT by_id
- * IMPORT matchers
- * IMPORT get_import_frame
- * IMPORT init_default_policy_dialog
- * IMPORTS_END
- */
+#FROM common/storage_client.js IMPORT get_remote_storage
+#FROM common/stored_types.js IMPORT TYPE_PREFIX, TYPE_NAME, \
+ list_prefixes
+#FROM common/misc.js IMPORT nice_name, matchers
+#FROM common/sanitize_JSON.js IMPORT parse_json_with_schema
+#FROM html/DOM_helpers.js IMPORT get_template, by_id
+#FROM html/import_frame.js IMPORT get_import_frame
var storage;
@@ -710,8 +702,6 @@ 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) {
diff --git a/html/reset.css b/html/reset.css
index dab51cd..ed11813 100644
--- a/html/reset.css
+++ b/html/reset.css
@@ -1,7 +1,6 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
- Copyright 2008,2011 Eric A. Meyer
- Redistribution terms are gathered in the `copyright' file.
+ License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
diff --git a/html/table.css b/html/table.css
index 36f88bb..1803e02 100644
--- a/html/table.css
+++ b/html/table.css
@@ -1,3 +1,32 @@
+/*
+ * SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0
+ *
+ * Table styling used in some Haketilo internal HTML pages
+ *
+ * This file is part of Haketilo.
+ *
+ * Copyright (C) 2021 Wojtek Kosior <koszko@koszko.org>
+ *
+ * File is dual-licensed. You can choose either GPLv3+, CC BY-SA or both.
+ *
+ * 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/>.
+ *
+ * I, Wojtek Kosior, thereby promise not to sue for violation of this file's
+ * licenses. Although I request that you do not make use this code in a
+ * proprietary program, I am not going to enforce this in court.
+ */
+
.table_wrapper {
display: block;
background-color: #f0f0f0;
diff --git a/manifest.json b/manifest.json
index ded085e..7a9edd5 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,19 +1,53 @@
// This is the manifest file of Haketilo.
//
// Copyright (C) 2021 Wojtek Kosior
-// Redistribution terms are gathered in the `copyright' file.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the CC0 1.0 Universal License as published by
+// the Creative Commons Corporation.
+//
+// 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
+// CC0 1.0 Universal License for more details.
+
+#IF NOT MOZILLA
+#IF NOT CHROMIUM
+#ERROR Target browser not selected! Please define 'MOZILLA' or 'CHROMIUM'.
+#ENDIF
+#ENDIF
+
{
+#IF MV2
"manifest_version": 2,
+#ELIF MV3
+ "manifest_version": 3,
+#ELSE
+#ERROR Manifest version not selected! Please define 'MV2'.
+#ENDIF
"name": "Haketilo",
"short_name": "Haketilo",
"version": "0.1",
"author": "Wojtek Kosior & contributors",
- "description": "Control your \"Web\" browsing.",_GECKO_APPLICATIONS_
+ "description": "Control your \"Web\" browsing.",
+#IF MOZILLA
+ "applications": {
+ "gecko": {
+ "id": "{6fe13369-88e9-440f-b837-5012fb3bedec}",
+ "strict_min_version": "60.0"
+ }
+ },
+#ENDIF
"icons":{
+#COPY_FILE icons/haketilo128.png
"128": "icons/haketilo128.png",
+#COPY_FILE icons/haketilo64.png
"64": "icons/haketilo64.png",
+#COPY_FILE icons/haketilo48.png
"48": "icons/haketilo48.png",
+#COPY_FILE icons/haketilo32.png
"32": "icons/haketilo32.png",
+#COPY_FILE icons/haketilo16.png
"16": "icons/haketilo16.png"
},
"permissions": [
@@ -38,16 +72,21 @@
"16": "icons/haketilo16.png"
},
"default_title": "Haketilo",
- "default_popup": "html/display-panel.html"
+#LOADHTML html/display_panel.html
+ "default_popup": "html/display_panel.html"
},
"options_ui": {
+#LOADHTML html/options.html
"page": "html/options.html",
"open_in_tab": true
- }_CHROMIUM_UPDATE_URL_,
+ },
+#COPY_FILE dummy
"web_accessible_resources": ["dummy"],
"background": {
"persistent": true,
- "scripts": [_BGSCRIPTS_]
+ "scripts": [
+#LOADJS background/main.js
+ ]
},
"content_scripts": [
{
@@ -55,7 +94,9 @@
"matches": ["<all_urls>"],
"match_about_blank": true,
"all_frames": true,
- "js": [_CONTENTSCRIPTS_]
+ "js": [
+#LOADJS content/main.js
+ ]
}
]
}
diff --git a/process_html_file.sh b/process_html_file.sh
deleted file mode 100755
index 1275b3e..0000000
--- a/process_html_file.sh
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/bin/sh
-
-# This file is part of Haketilo
-#
-# Copyright (C) 2021, Wojtek Kosior
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the CC0 1.0 Universal License as published by
-# the Creative Commons Corporation.
-#
-# 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
-# CC0 1.0 Universal License for more details.
-
-# Call like:
-# ./process_html_file.sh html/options.html
-
-. ./shell_utils.sh
-
-FILE="$1"
-FILEKEY=$(sanitize "$FILE")
-
-if [ "x$(map_get HTML_FILENAMES $FILEKEY)" = "xyes" ]; then
- printf 'import loop on %s\n' "$FILE" >&2
- exit 1
-fi
-
-map_set_export HTML_FILENAMES $FILEKEY yes
-
-awk '\
-!/^[\t\r ]*<IMPORT[\t\r ]+([^\t\r ]+)[\t\r ]+\/>[\t\r ]*$/{
- print $0;
-}
-/^[\t\r ]*<IMPORT[\t\r ]+([^\t\r ]+)[\t\r ]+\/>[\t\r ]*$/{
- indent = substr($0, 1, index($0, "<") - 1);
- command = "./process_html_file.sh " $2;
- while (command | getline) {
- print indent $0;
- }
- if (close(command) != 0)
- exit 1;
-}' < "$FILE"
diff --git a/test/script_loader.py b/test/script_loader.py
index 8f30944..edf8143 100644
--- a/test/script_loader.py
+++ b/test/script_loader.py
@@ -41,54 +41,29 @@ def make_relative_path(path):
return path
-"""Used to ignore hidden files and emacs auto-save files."""
-script_name_regex = re.compile(r'^[^.#].*\.js$')
+script_cache = {}
-def available_scripts(directory):
- for script in directory.rglob('*.js'):
- if script_name_regex.match(script.name):
- yield script
-
-def wrapped_script(script_path, wrap_partially=True):
- if script_path == 'exports_init.js':
- if not (script_root / 'exports_init.js').exists():
- subprocess.run([str(script_root / 'write_exports_init.sh'),
- 'mozilla', '.', 'default_settings.json'],
- cwd=script_root, check=True)
-
- with open(script_root / 'exports_init.js') as script:
- return script.read()
-
- command = 'partially_wrapped_code' if wrap_partially else 'wrapped_code'
- awk_command = ['awk', '-f', str(awk_script), command, str(script_path)]
- awk = subprocess.run(awk_command, stdout=subprocess.PIPE, cwd=script_root,
- check=True)
-
- return awk.stdout.decode()
-
-def load_script(path, import_dirs):
+def load_script(path):
"""
- `path` and `import_dirs` are .js file path and a list of directory paths,
- respectively. They may be absolute or specified relative to Haketilo's
- project directory.
+ `path` is a .js file path in Haketilo sources. It may be absolute or
+ specified relative to Haketilo's project directory.
Return a string containing script from `path` together with all other
- scripts it depends. Dependencies are wrapped in the same way Haketilo's
+ scripts it depends on. Dependencies are wrapped in the same way Haketilo's
build system wraps them, with imports properly satisfied. The main script
being loaded is wrapped partially - it also has its imports satisfied, but
- its code is not placed inside an anonymous function, so the
+ its code is executed in global scope instead of within an anonymous function
+ and imported variables are defined with `let` instead of `const` to allow
+ a dependency to be substituted by a mocked value.
"""
path = make_relative_path(path)
+ if str(path) in script_cache:
+ return script_cache[str(path)]
- import_dirs = [make_relative_path(dir) for dir in import_dirs]
- available = [s for dir in import_dirs for s in available_scripts(dir)]
-
- awk = subprocess.run(['awk', '-f', str(awk_script), 'script_dependencies',
- str(path), *[str(s) for s in available]],
+ awk = subprocess.run(['awk', '-f', str(awk_script), '--', '-D', 'MOZILLA',
+ '-D', 'MV2', '--output=amalgamate-js:' + str(path)],
stdout=subprocess.PIPE, cwd=script_root, check=True)
+ script = awk.stdout.decode()
+ script_cache[str(path)] = script
- to_load = awk.stdout.decode().split()
- texts = [wrapped_script(path, wrap_partially=(i == len(to_load) - 1))
- for i, path in enumerate(to_load)]
-
- return '\n'.join(texts)
+ return script
diff --git a/test/unit/conftest.py b/test/unit/conftest.py
index eec311c..f9a17f8 100644
--- a/test/unit/conftest.py
+++ b/test/unit/conftest.py
@@ -33,7 +33,6 @@ from selenium.webdriver.support import expected_conditions as EC
from ..profiles import firefox_safe_mode
from ..server import do_an_internet
-from ..script_loader import load_script
from ..extension_crafting import make_extension
@pytest.fixture(scope="package")
@@ -140,13 +139,6 @@ def execute_in_page(driver):
yield do_execute
@pytest.fixture()
-def load_into_page(driver):
- def do_load(path, import_dirs, *args):
- _execute_in_page_context(driver, load_script(path, import_dirs), args)
-
- yield do_load
-
-@pytest.fixture()
def wait_elem_text(driver):
def do_wait(id, text):
WebDriverWait(driver, 10).until(
diff --git a/test/unit/test_basic.py b/test/unit/test_basic.py
index ca956e7..2564e9d 100644
--- a/test/unit/test_basic.py
+++ b/test/unit/test_basic.py
@@ -19,6 +19,8 @@ Haketilo unit tests - base
import pytest
+from ..script_loader import load_script
+
def test_driver(driver):
"""
A trivial test case that verifies mocked web pages served by proxy can be
@@ -32,12 +34,12 @@ def test_driver(driver):
assert "Schrodinger's Document" in title
@pytest.mark.get_page('https://gotmyowndoma.in')
-def test_script_loader(execute_in_page, load_into_page):
+def test_script_loader(execute_in_page):
"""
A trivial test case that verifies Haketilo's .js files can be properly
loaded into a test page together with their dependencies.
"""
- load_into_page('common/stored_types.js', ['common'])
+ execute_in_page(load_script('common/stored_types.js'))
assert execute_in_page('returnval(TYPE_PREFIX.VAR);') == '_'
diff --git a/test/unit/test_broadcast.py b/test/unit/test_broadcast.py
index 11a61b0..1213b17 100644
--- a/test/unit/test_broadcast.py
+++ b/test/unit/test_broadcast.py
@@ -21,12 +21,9 @@ import pytest
from ..script_loader import load_script
-def broker_js():
- js = load_script('background/broadcast_broker.js', ['common', 'background'])
- return js + ';start_broadcast_broker();'
+broker_js = lambda: load_script('background/broadcast_broker.js') + ';start();'
-def broadcast_js():
- return load_script('common/broadcast.js', ['common'])
+broadcast_js = lambda: load_script('common/broadcast.js')
test_page_html = '''
<!DOCTYPE html>
@@ -53,7 +50,6 @@ def test_broadcast(driver, execute_in_page, wait_elem_text):
API and implemented in `background/broadcast_broker.js` and
`common/broadcast.js` works correctly.
"""
-
# The broadcast facility is meant to enable message distribution between
# multiple contexts (e.g. different tabs/windows). Let's open the same
# extension's test page in a second window.
@@ -71,15 +67,15 @@ def test_broadcast(driver, execute_in_page, wait_elem_text):
'''
const divs = [0, 1, 2].map(n => document.getElementById("d" + n));
let appender = n => (t => divs[n].append("\\n" + `[${t[0]}, ${t[1]}]`));
- let listener0 = broadcast.listener_connection(appender(0));
- broadcast.subscribe(listener0, "somebodyoncetoldme");
+ let listener0 = listener_connection(appender(0));
+ subscribe(listener0, "somebodyoncetoldme");
''')
driver.switch_to.window(windows[1])
execute_in_page(
'''
- let sender0 = broadcast.sender_connection();
- broadcast.out(sender0, "somebodyoncetoldme", "iaintthesharpesttool");
+ let sender0 = sender_connection();
+ out(sender0, "somebodyoncetoldme", "iaintthesharpesttool");
''')
driver.switch_to.window(windows[0])
@@ -89,11 +85,11 @@ def test_broadcast(driver, execute_in_page, wait_elem_text):
driver.switch_to.window(windows[0])
execute_in_page(
'''
- let listener1 = broadcast.listener_connection(appender(1));
- broadcast.subscribe(listener1, "worldisgonnarollme");
- let listener2 = broadcast.listener_connection(appender(2));
- broadcast.subscribe(listener2, "worldisgonnarollme");
- broadcast.subscribe(listener2, "somebodyoncetoldme");
+ let listener1 = listener_connection(appender(1));
+ subscribe(listener1, "worldisgonnarollme");
+ let listener2 = listener_connection(appender(2));
+ subscribe(listener2, "worldisgonnarollme");
+ subscribe(listener2, "somebodyoncetoldme");
''')
# Let's send one message to one channel and one to the other. Verify they
@@ -101,8 +97,8 @@ def test_broadcast(driver, execute_in_page, wait_elem_text):
driver.switch_to.window(windows[1])
execute_in_page(
'''
- broadcast.out(sender0, "somebodyoncetoldme", "intheshed");
- broadcast.out(sender0, "worldisgonnarollme", "shewaslooking");
+ out(sender0, "somebodyoncetoldme", "intheshed");
+ out(sender0, "worldisgonnarollme", "shewaslooking");
''')
driver.switch_to.window(windows[0])
@@ -121,9 +117,9 @@ def test_broadcast(driver, execute_in_page, wait_elem_text):
driver.switch_to.window(windows[2])
execute_in_page(
'''
- let sender1 = broadcast.sender_connection();
- broadcast.prepare(sender1, "somebodyoncetoldme", "kindadumb");
- broadcast.out(sender1, "worldisgonnarollme", "withherfinger");
+ let sender1 = sender_connection();
+ prepare(sender1, "somebodyoncetoldme", "kindadumb");
+ out(sender1, "worldisgonnarollme", "withherfinger");
''')
driver.switch_to.window(windows[0])
@@ -132,7 +128,7 @@ def test_broadcast(driver, execute_in_page, wait_elem_text):
assert 'kindadumb' not in text
driver.switch_to.window(windows[2])
- execute_in_page('broadcast.flush(sender1);')
+ execute_in_page('flush(sender1);')
driver.switch_to.window(windows[0])
wait_elem_text('d0', 'kindadumb')
@@ -142,10 +138,10 @@ def test_broadcast(driver, execute_in_page, wait_elem_text):
driver.switch_to.window(windows[2])
execute_in_page(
'''
- broadcast.prepare(sender1, "somebodyoncetoldme", "andherthumb");
- broadcast.discard(sender1);
- broadcast.prepare(sender1, "somebodyoncetoldme", "andhermiddlefinger");
- broadcast.flush(sender1);
+ prepare(sender1, "somebodyoncetoldme", "andherthumb");
+ discard(sender1);
+ prepare(sender1, "somebodyoncetoldme", "andhermiddlefinger");
+ flush(sender1);
''')
driver.switch_to.window(windows[0])
@@ -158,7 +154,7 @@ def test_broadcast(driver, execute_in_page, wait_elem_text):
driver.switch_to.window(windows[2])
execute_in_page(
'''
- broadcast.prepare(sender1, "worldisgonnarollme", "intheshape", 500);
+ prepare(sender1, "worldisgonnarollme", "intheshape", 500);
''')
driver.close()
@@ -166,11 +162,11 @@ def test_broadcast(driver, execute_in_page, wait_elem_text):
wait_elem_text('d2', 'intheshape')
# Verify listener's connection gets closed properly.
- execute_in_page('broadcast.close(listener0); broadcast.close(listener1);')
+ execute_in_page('close(listener0); close(listener1);')
driver.switch_to.window(windows[1])
- execute_in_page('broadcast.out(sender0, "worldisgonnarollme", "ofanL");')
- execute_in_page('broadcast.out(sender0, "somebodyoncetoldme", "forehead");')
+ execute_in_page('out(sender0, "worldisgonnarollme", "ofanL");')
+ execute_in_page('out(sender0, "somebodyoncetoldme", "forehead");')
driver.switch_to.window(windows[0])
wait_elem_text('d2', 'ofanL')
diff --git a/test/unit/test_indexeddb.py b/test/unit/test_indexeddb.py
index 3604ee9..af60e1c 100644
--- a/test/unit/test_indexeddb.py
+++ b/test/unit/test_indexeddb.py
@@ -27,8 +27,8 @@ from selenium.common.exceptions import WebDriverException
from ..script_loader import load_script
-def indexeddb_js():
- return load_script('common/indexeddb.js', ['common'])
+indexeddb_js = lambda: load_script('common/indexeddb.js')
+broker_js = lambda: load_script('background/broadcast_broker.js') + ';start();'
def sample_file(contents):
return {
@@ -118,7 +118,7 @@ def test_haketilodb_save_remove(execute_in_page):
'''
async function get_database_contents()
{
- const db = await haketilodb.get();
+ const db = await get_db();
const transaction = db.transaction(db.objectStoreNames);
const store_names_reqs = [...db.objectStoreNames]
@@ -226,7 +226,7 @@ def test_haketilodb_save_remove(execute_in_page):
for i, item_type in enumerate(['resource', 'mapping']):
results[i] = execute_in_page(
f'''{{
- const remover = haketilodb.remove_{item_type};
+ const remover = remove_{item_type};
const promise =
start_items_transaction(["{item_type}s"], {{}})
.then(ctx => remover('helloapple', ctx).then(() => ctx))
@@ -282,10 +282,6 @@ def test_haketilodb_save_remove(execute_in_page):
assert database_contents['resources'] == [sample_resource]
assert database_contents['mappings'] == [sample_mapping]
-def broker_js():
- js = load_script('background/broadcast_broker.js', ['common', 'background'])
- return js + ';start_broadcast_broker();'
-
test_page_html = '''
<!DOCTYPE html>
<script src="/testpage.js"></script>
@@ -336,8 +332,7 @@ def test_haketilodb_track(driver, execute_in_page, wait_elem_text):
driver.switch_to.window(window)
execute_in_page('initial_data = arguments[0];', initial_data)
- # See if haketilodb.track_*() functions properly return the already-existing
- # items.
+ # See if track_*() functions properly return the already-existing items.
execute_in_page(
'''
function update_item(store_name, change)
@@ -361,9 +356,9 @@ def test_haketilodb_track(driver, execute_in_page, wait_elem_text):
const update_mapping = change => update_item("mappings", change);
[resource_tracking, resource_items] =
- await haketilodb.track_resources(update_resource);
+ await track_resources(update_resource);
[mapping_tracking, mapping_items] =
- await haketilodb.track_mappings(update_mapping);
+ await track_mappings(update_mapping);
for (const item of resource_items)
update_resource({identifier: item.identifier, new_val: item});
@@ -404,8 +399,7 @@ def test_haketilodb_track(driver, execute_in_page, wait_elem_text):
},
'files': sample_files_by_hash
}
- execute_in_page('returnval(haketilodb.save_items(arguments[0]));',
- sample_data)
+ execute_in_page('returnval(save_items(arguments[0]));', sample_data)
driver.switch_to.window(windows[0])
driver.implicitly_wait(10)
@@ -421,11 +415,11 @@ def test_haketilodb_track(driver, execute_in_page, wait_elem_text):
'''{
async function remove_items()
{
- const store_names = ["resources", "mappings"];
- const ctx = await haketilodb.start_items_transaction(store_names, {});
- await haketilodb.remove_resource("helloapple", ctx);
- await haketilodb.remove_mapping("helloapple-copy", ctx);
- await haketilodb.finalize_items_transaction(ctx);
+ const store_names = ["resources", "mappings"];
+ const ctx = await start_items_transaction(store_names, {});
+ await remove_resource("helloapple", ctx);
+ await remove_mapping("helloapple-copy", ctx);
+ await finalize_items_transaction(ctx);
}
returnval(remove_items());
}''')
diff --git a/test/unit/test_patterns.py b/test/unit/test_patterns.py
index 99e1ed5..f2eeaf8 100644
--- a/test/unit/test_patterns.py
+++ b/test/unit/test_patterns.py
@@ -21,17 +21,13 @@ import pytest
from ..script_loader import load_script
-@pytest.fixture(scope="session")
-def patterns_code():
- yield load_script('common/patterns.js', ['common'])
-
@pytest.mark.get_page('https://gotmyowndoma.in')
-def test_regexes(execute_in_page, patterns_code):
+def test_regexes(execute_in_page):
"""
patterns.js contains regexes used for URL parsing.
Verify they work properly.
"""
- execute_in_page(patterns_code)
+ execute_in_page(load_script('common/patterns.js'))
valid_url = 'https://example.com/a/b?ver=1.2.3#heading2'
valid_url_rest = 'example.com/a/b?ver=1.2.3#heading2'
@@ -92,12 +88,12 @@ def test_regexes(execute_in_page, patterns_code):
assert match is None
@pytest.mark.get_page('https://gotmyowndoma.in')
-def test_deconstruct_url(execute_in_page, patterns_code):
+def test_deconstruct_url(execute_in_page):
"""
patterns.js contains deconstruct_url() function that handles URL parsing.
Verify it works properly.
"""
- execute_in_page(patterns_code)
+ execute_in_page(load_script('common/patterns.js'))
deco = execute_in_page('returnval(deconstruct_url(arguments[0]));',
'https://eXaMpLe.com/a/b?ver=1.2.3#heading2')
diff --git a/test/unit/test_patterns_query_tree.py b/test/unit/test_patterns_query_tree.py
index a67e22f..80bf554 100644
--- a/test/unit/test_patterns_query_tree.py
+++ b/test/unit/test_patterns_query_tree.py
@@ -21,18 +21,14 @@ import pytest
from ..script_loader import load_script
-@pytest.fixture(scope="session")
-def patterns_tree_code():
- yield load_script('common/patterns_query_tree.js', ['common'])
-
@pytest.mark.get_page('https://gotmyowndoma.in')
-def test_modify_branch(execute_in_page, patterns_tree_code):
+def test_modify_branch(execute_in_page):
"""
patterns_query_tree.js contains Pattern Tree data structure that allows
arrays of string labels to be mapped to items.
Verify operations modifying a single branch of such tree work properly.
"""
- execute_in_page(patterns_tree_code)
+ execute_in_page(load_script('common/patterns_query_tree.js'))
execute_in_page(
'''
let items_added;
@@ -197,13 +193,13 @@ def test_modify_branch(execute_in_page, patterns_tree_code):
}
@pytest.mark.get_page('https://gotmyowndoma.in')
-def test_search_branch(execute_in_page, patterns_tree_code):
+def test_search_branch(execute_in_page):
"""
patterns_query_tree.js contains Pattern Tree data structure that allows
arrays of string labels to be mapped to items.
Verify searching a single branch of such tree work properly.
"""
- execute_in_page(patterns_tree_code)
+ execute_in_page(load_script('common/patterns_query_tree.js'))
execute_in_page(
'''
const item_adder = item => (array => [...(array || []), item]);
@@ -285,13 +281,13 @@ def test_search_branch(execute_in_page, patterns_tree_code):
raise e from None
@pytest.mark.get_page('https://gotmyowndoma.in')
-def test_pattern_tree(execute_in_page, patterns_tree_code):
+def test_pattern_tree(execute_in_page):
"""
patterns_query_tree.js contains Pattern Tree data structure that allows
arrays of string labels to be mapped to items.
Verify operations on entire such tree work properly.
"""
- execute_in_page(patterns_tree_code)
+ execute_in_page(load_script('common/patterns_query_tree.js'))
# Perform tests with all possible patterns for a simple URL.
url = 'https://example.com'
@@ -315,12 +311,12 @@ def test_pattern_tree(execute_in_page, patterns_tree_code):
tree, result = execute_in_page(
'''{
- const tree = pattern_tree.make();
+ const tree = pattern_tree_make();
for (const pattern of arguments[0].concat(arguments[1])) {
- pattern_tree.register(tree, pattern, 'key', pattern);
- pattern_tree.register(tree, pattern + '/', 'key', pattern + '/');
+ pattern_tree_register(tree, pattern, 'key', pattern);
+ pattern_tree_register(tree, pattern + '/', 'key', pattern + '/');
}
- returnval([tree, [...pattern_tree.search(tree, arguments[2])]]);
+ returnval([tree, [...pattern_tree_search(tree, arguments[2])]]);
}''',
patterns, bad_patterns, url)
assert expected == result
@@ -333,10 +329,10 @@ def test_pattern_tree(execute_in_page, patterns_tree_code):
'''{
const tree = arguments[0];
for (const pattern of arguments[1]) {
- pattern_tree.deregister(tree, pattern, 'key');
- pattern_tree.deregister(tree, pattern + '/', 'key');
+ pattern_tree_deregister(tree, pattern, 'key');
+ pattern_tree_deregister(tree, pattern + '/', 'key');
}
- returnval([tree, [...pattern_tree.search(tree, arguments[2])]]);
+ returnval([tree, [...pattern_tree_search(tree, arguments[2])]]);
}''',
tree, patterns_removed, url)
assert expected == result
@@ -346,8 +342,8 @@ def test_pattern_tree(execute_in_page, patterns_tree_code):
'''{
const tree = arguments[0];
for (const pattern of arguments[1].concat(arguments[2])) {
- pattern_tree.deregister(tree, pattern, 'key');
- pattern_tree.deregister(tree, pattern + '/', 'key');
+ pattern_tree_deregister(tree, pattern, 'key');
+ pattern_tree_deregister(tree, pattern + '/', 'key');
}
returnval(tree);
}''',
@@ -439,12 +435,12 @@ def test_pattern_tree(execute_in_page, patterns_tree_code):
tree, result = execute_in_page(
'''{
- const tree = pattern_tree.make();
+ const tree = pattern_tree_make();
for (const pattern of arguments[0].concat(arguments[1])) {
- pattern_tree.register(tree, pattern, 'key', pattern);
- pattern_tree.register(tree, pattern + '/', 'key', pattern + '/');
+ pattern_tree_register(tree, pattern, 'key', pattern);
+ pattern_tree_register(tree, pattern + '/', 'key', pattern + '/');
}
- returnval([tree, [...pattern_tree.search(tree, arguments[2])]]);
+ returnval([tree, [...pattern_tree_search(tree, arguments[2])]]);
}''',
patterns, bad_patterns, url)
assert expected == result
@@ -456,8 +452,8 @@ def test_pattern_tree(execute_in_page, patterns_tree_code):
'''{
const tree = arguments[0];
for (const pattern of arguments[1])
- pattern_tree.deregister(tree, pattern + '/', 'key');
- returnval([tree, [...pattern_tree.search(tree, arguments[2])]]);
+ pattern_tree_deregister(tree, pattern + '/', 'key');
+ returnval([tree, [...pattern_tree_search(tree, arguments[2])]]);
}''',
tree, patterns, url)
assert expected == result
@@ -467,10 +463,10 @@ def test_pattern_tree(execute_in_page, patterns_tree_code):
'''{
const tree = arguments[0];
for (const pattern of arguments[1])
- pattern_tree.deregister(tree, pattern, 'key');
+ pattern_tree_deregister(tree, pattern, 'key');
for (const pattern of arguments[2]) {
- pattern_tree.deregister(tree, pattern, 'key');
- pattern_tree.deregister(tree, pattern + '/', 'key');
+ pattern_tree_deregister(tree, pattern, 'key');
+ pattern_tree_deregister(tree, pattern + '/', 'key');
}
returnval(tree);
}''',