From b590eaa2f64ead3384eadc6fe58f6358aa1a0478 Mon Sep 17 00:00:00 2001 From: Wojtek Kosior Date: Wed, 22 Dec 2021 16:39:34 +0100 Subject: reworked build system; added missing license notices --- CHROMIUM_exports_init.js | 3 - MOZILLA_exports_init.js | 57 --- README.md | 44 ++ README.txt | 33 -- background/broadcast_broker.js | 24 +- background/main.js | 50 +-- background/page_actions_server.js | 30 +- background/policy_injector.js | 20 +- background/storage.js | 28 +- background/storage_server.js | 22 +- background/stream_filter.js | 17 +- build.sh | 149 +------ common/ajax.js | 6 +- common/broadcast.js | 29 +- common/browser.js | 26 ++ common/connection_types.js | 18 +- common/entities.js | 15 +- common/indexeddb.js | 63 ++- common/lock.js | 17 +- common/message_server.js | 12 +- common/misc.js | 43 +- common/observable.js | 65 --- common/observables.js | 61 +++ common/once.js | 6 +- common/patterns.js | 13 +- common/patterns_query_tree.js | 27 +- common/sanitize_JSON.js | 6 +- common/settings_query.js | 27 +- common/sha256.js | 8 +- common/storage_client.js | 21 +- common/storage_light.js | 39 +- common/storage_raw.js | 33 +- common/stored_types.js | 12 +- compute_scripts.awk | 748 ++++++++++++++++++++++++++++------ content/activity_info_server.js | 34 +- content/main.js | 29 +- content/page_actions.js | 19 +- content/repo_query.js | 30 +- copyright | 17 +- html/DOM_helpers.js | 15 +- html/MOZILLA_scrollbar_fix.css | 48 --- html/back_button.css | 28 +- html/base.css | 29 +- html/default_blocking_policy.html | 32 +- html/default_blocking_policy.js | 17 +- html/display-panel.html | 347 ---------------- html/display-panel.js | 590 --------------------------- html/display_panel.html | 375 +++++++++++++++++ html/display_panel.js | 586 ++++++++++++++++++++++++++ html/import_frame.html | 31 +- html/import_frame.js | 21 +- html/mozilla_scrollbar_fix.css | 67 +++ html/options.html | 38 +- html/options_main.js | 24 +- html/reset.css | 3 +- html/table.css | 29 ++ manifest.json | 53 ++- process_html_file.sh | 43 -- test/script_loader.py | 55 +-- test/unit/conftest.py | 8 - test/unit/test_basic.py | 6 +- test/unit/test_broadcast.py | 54 ++- test/unit/test_indexeddb.py | 32 +- test/unit/test_patterns.py | 12 +- test/unit/test_patterns_query_tree.py | 52 ++- 65 files changed, 2360 insertions(+), 2136 deletions(-) delete mode 100644 CHROMIUM_exports_init.js delete mode 100644 MOZILLA_exports_init.js create mode 100644 README.md delete mode 100644 README.txt create mode 100644 common/browser.js delete mode 100644 common/observable.js create mode 100644 common/observables.js mode change 100644 => 100755 compute_scripts.awk delete mode 100644 html/MOZILLA_scrollbar_fix.css delete mode 100644 html/display-panel.html delete mode 100644 html/display-panel.js create mode 100644 html/display_panel.html create mode 100644 html/display_panel.js create mode 100644 html/mozilla_scrollbar_fix.css delete mode 100755 process_html_file.sh 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 . - * - * 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 ' "$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/observable.js deleted file mode 100644 index 56d0ed6..0000000 --- a/common/observable.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * This file is part of Haketilo. - * - * Function: Facilitate listening to (internal, self-generated) events. - * - * 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 . - * - * 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. - */ - -const make = (value=undefined) => ({value, listeners: new Set()}); -const subscribe = (observable, cb) => observable.listeners.add(cb); -const unsubscribe = (observable, cb) => observable.listeners.delete(cb); - -const silent_set = (observable, value) => observable.value = value; -const broadcast = (observable, ...values) => - observable.listeners.forEach(cb => cb(...values)); - -function set(observable, value) -{ - const old_value = 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 - */ diff --git a/common/observables.js b/common/observables.js new file mode 100644 index 0000000..f1db88c --- /dev/null +++ b/common/observables.js @@ -0,0 +1,61 @@ +/** + * This file is part of Haketilo. + * + * Function: Facilitate listening to (internal, self-generated) events. + * + * 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 . + * + * 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. + */ + +#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) +{ + const old_value = observable.value; + silent_set(observable, value); + broadcast(observable, value, old_value); +} +#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 old mode 100644 new mode 100755 index 3d8a5b0..9edc56d --- 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 = "" + } 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 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 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 -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 - 2021 Nicholas Johnson -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 + * + * 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 . * - * 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 + * + * 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 . * - * 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 @@ + + + - - - - - -
-

Site modifiers install

-
- -
-
- - -
-

Injected scripts

-
- None -
-
- - -
-

Possible patterns for this page

-
- -
-
-
- - - -
-
-
-
- - - -
-
- - -
-

Queried from repositories

-
-
-
- - -
-

-

Privileged page

- -
-
- -
-
- -
- - - - - - - - - - - - - - - - - - - - - -
Matched pattern:... - -
Scripts blocked:...
Injected payload:... - -
- -
- This is a non-HTML page. Chosen payload will not be injected. -
- -
-
-

- Connecting to content script... -

- -
-
-
- - -
- -
- - _POPUPSCRIPTS_ - - diff --git a/html/display-panel.js b/html/display-panel.js deleted file mode 100644 index d48d254..0000000 --- a/html/display-panel.js +++ /dev/null @@ -1,590 +0,0 @@ -/** - * This file is part of Haketilo. - * - * Function: Popup logic. - * - * 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 . - * - * 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. - */ - -/* - * 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 - */ - -let storage; -let tab_url; - -/* Force popup '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 show_queried_view_radio = by_id("show_queried_view_radio"); - -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); - - try { - return (await promise)[0]; - } catch(e) { - console.log(e); - } -} - -const page_url_heading = by_id("page_url_heading"); -const privileged_notice = by_id("privileged_notice"); -const page_state = by_id("page_state"); - -/* Helper functions to convert string into a list of one-letter 's. */ -function char_to_span(char, doc) -{ - const span = document.createElement("span"); - span.textContent = char; - return span; -} - -function to_spans(string, doc=document) -{ - return string.split("").map(c => char_to_span(c, doc)); -} - -async function show_page_activity_info() -{ - const tab = await get_current_tab(); - - if (tab === undefined) { - page_url_heading.textContent = "unknown page"; - return; - } - - tab_url = /^([^?#]*)/.exec(tab.url)[1]; - to_spans(tab_url).forEach(s => page_url_heading.append(s)); - if (is_privileged_url(tab_url)) { - privileged_notice.classList.remove("hide"); - return; - } - - populate_possible_patterns_list(tab_url); - page_state.classList.remove("hide"); - - try_to_connect(tab.id); -} - -const possible_patterns_list = by_id("possible_patterns"); -const known_patterns = new Map(); - -function add_pattern_to_list(pattern) -{ - const template = clone_template("pattern_entry"); - template.name.textContent = pattern; - - const settings_opener = () => open_in_settings(TYPE_PREFIX.PAGE, pattern); - template.button.addEventListener("click", settings_opener); - - known_patterns.set(pattern, template); - possible_patterns_list.append(template.entry); - - return template; -} - -function style_possible_pattern_entry(pattern, exists_in_settings) -{ - const [text, class_action] = exists_in_settings ? - ["Edit", "add"] : ["Add", "remove"]; - const entry_object = known_patterns.get(pattern); - - if (entry_object) { - entry_object.button.textContent = `${text} setting`; - entry_object.entry.classList[class_action]("matched_pattern"); - } -} - -function handle_page_change(change) -{ - style_possible_pattern_entry(change.item, change.new_val !== undefined); -} - -function populate_possible_patterns_list(url) -{ - for (const pattern of each_url_pattern(url)) - add_pattern_to_list(pattern); - - for (const [pattern, settings] of query_all(storage, url)) - style_possible_pattern_entry(pattern, true); - - storage.add_change_listener(handle_page_change, [TYPE_PREFIX.PAGE]); -} - -const connected_chbx = by_id("connected_chbx"); -const query_pattern_but = by_id("query_pattern"); - -var content_script_port; - -function try_to_connect(tab_id) -{ - /* This won't connect to iframes. We'll add support for them later */ - const connect_info = {name: CONNECTION_TYPE.ACTIVITY_INFO, frameId: 0}; - content_script_port = browser.tabs.connect(tab_id, connect_info); - - const disconnect_cb = () => handle_disconnect(tab_id, start_querying_repos); - content_script_port.onDisconnect.addListener(disconnect_cb); - content_script_port.onMessage.addListener(handle_activity_report); - - query_pattern_but.addEventListener("click", start_querying_repos); - - if (is_mozilla) - setTimeout(() => monitor_connecting(tab_id), 1000); -} - -function start_querying_repos() -{ - query_pattern_but.removeEventListener("click", start_querying_repos); - const repo_urls = storage.get_all_names(TYPE_PREFIX.REPO); - if (content_script_port) - content_script_port.postMessage([TYPE_PREFIX.URL, tab_url, repo_urls]); -} - -const loading_point = by_id("loading_point"); -const reload_notice = by_id("reload_notice"); - -function handle_disconnect(tab_id, button_cb) -{ - query_pattern_but.removeEventListener("click", button_cb); - content_script_port = null; - - if (is_chrome && !browser.runtime.lastError) - return; - - /* return if error was not during connection initialization */ - if (connected_chbx.checked) - return; - - loading_point.classList.toggle("camouflage"); - reload_notice.classList.remove("hide"); - - setTimeout(() => try_to_connect(tab_id), 1000); -} - -function monitor_connecting(tab_id) -{ - if (connected_chbx.checked) - return; - - if (content_script_port) - content_script_port.disconnect(); - else - return; - - loading_point.classList.toggle("camouflage"); - reload_notice.classList.remove("hide"); - try_to_connect(tab_id); -} - -const pattern_span = by_id("pattern"); -const view_pattern_but = by_id("view_pattern"); -const blocked_span = by_id("blocked"); -const payload_span = by_id("payload"); -const payload_buttons_div = by_id("payload_buttons"); -const view_payload_but = by_id("view_payload"); -const view_injected_but = by_id("view_injected"); -const container_for_injected = by_id("container_for_injected"); -const content_type_cell = by_id("content_type"); - -const queried_items = new Map(); - -let max_injected_script_id = 0; - -function handle_activity_report(message) -{ - connected_chbx.checked = true; - - const [type, data] = message; - - if (type === "settings") { - const settings = data; - - blocked_span.textContent = settings.allow ? "no" : "yes"; - - if (settings.pattern) { - pattern_span.textContent = pattern; - const settings_opener = - () => open_in_settings(TYPE_PREFIX.PAGE, settings.pattern); - view_pattern_but.classList.remove("hide"); - view_pattern_but.addEventListener("click", settings_opener); - } else { - pattern_span.textContent = "none"; - blocked_span.textContent = blocked_span.textContent + " (default)"; - } - - if (settings.payload) { - payload_span.textContent = nice_name(...settings.payload); - payload_buttons_div.classList.remove("hide"); - const settings_opener = () => open_in_settings(...settings.payload); - view_payload_but.addEventListener("click", settings_opener); - } else { - payload_span.textContent = "none"; - } - } - if (type === "script") { - const template = clone_template("injected_script"); - const chbx_id = `injected_script_${max_injected_script_id++}`; - template.chbx.id = chbx_id; - template.lbl.setAttribute("for", chbx_id); - template.script_contents.textContent = data; - container_for_injected.appendChild(template.div); - } - if (type === "is_html") { - if (!data) - content_type_cell.classList.remove("hide"); - } - if (type === "repo_query_action") { - const key = data.prefix + data.item; - const results = queried_items.get(key) || {}; - Object.assign(results, data.results); - queried_items.set(key, results); - - const action = data.prefix === TYPE_PREFIX.URL ? - show_query_result : record_fetched_install_dep; - - for (const [repo_url, result] of Object.entries(data.results)) - action(data.prefix, data.item, repo_url, result); - } -} - -const container_for_repo_responses = by_id("container_for_repo_responses"); - -const results_lists = new Map(); - -function create_results_list(url) -{ - const cloned_template = clone_template("multi_repos_query_result"); - cloned_template.url_span.textContent = url; - container_for_repo_responses.appendChild(cloned_template.div); - - cloned_template.by_repo = new Map(); - results_lists.set(url, cloned_template); - - return cloned_template; -} - -function create_result_item(list_object, repo_url, result) -{ - const cloned_template = clone_template("single_repo_query_result"); - cloned_template.repo_url.textContent = repo_url; - cloned_template.appended = null; - - list_object.ul.appendChild(cloned_template.li); - list_object.by_repo.set(repo_url, cloned_template); - - return cloned_template; -} - -function set_appended(result_item, element) -{ - if (result_item.appended) - result_item.appended.remove(); - result_item.appended = element; - result_item.li.appendChild(element); -} - -function show_message(result_item, text) -{ - const div = document.createElement("div"); - div.textContent = text; - set_appended(result_item, div); -} - -function showcb(text) -{ - return item => show_message(item, text); -} - -function unroll_chbx_first_checked(entry_object) -{ - if (!entry_object.chbx.checked) - return; - - entry_object.chbx.removeEventListener("change", entry_object.unroll_cb); - delete entry_object.unroll_cb; - - entry_object.unroll.innerHTML = "preview not implemented...
(consider contributing)"; -} - -let import_frame; -let install_target = null; - -function install_abort(error_state) -{ - import_frame.show_error(`Error: ${error_state}`); - install_target = null; -} - -/* - * Translate objects from the format in which they are sent by Hydrilla to the - * format in which they are stored in settings. - */ - -function translate_script(script_object, repo_url) -{ - return { - [TYPE_PREFIX.SCRIPT + script_object.name]: { - hash: script_object.sha256, - url: `${repo_url}/content/${script_object.location}` - } - }; -} - -function translate_bag(bag_object) -{ - return { - [TYPE_PREFIX.BAG + bag_object.name]: bag_object.components - }; -} - -const format_translators = { - [TYPE_PREFIX.BAG]: translate_bag, - [TYPE_PREFIX.SCRIPT]: translate_script -}; - -function install_check_ready() -{ - if (install_target.to_fetch.size > 0) - return; - - const page_key = [TYPE_PREFIX.PAGE + install_target.pattern]; - const to_install = [{[page_key]: {components: install_target.payload}}]; - - for (const key of install_target.fetched) { - const old_object = - queried_items.get(key)[install_target.repo_url].response; - const new_object = - format_translators[key[0]](old_object, install_target.repo_url); - to_install.push(new_object); - } - - import_frame.show_selection(to_install); -} - -const possible_errors = ["connection_error", "parse_error"]; - -function fetch_install_deps(components) -{ - const needed = [...components]; - const processed = new Set(); - - while (needed.length > 0) { - const [prefix, item] = needed.pop(); - const key = prefix + item; - processed.add(key); - const results = queried_items.get(key); - let relevant_result = null; - - if (results) - relevant_result = results[install_target.repo_url]; - - if (!relevant_result) { - content_script_port.postMessage([prefix, item, - [install_target.repo_url]]); - install_target.to_fetch.add(key); - continue; - } - - if (possible_errors.includes(relevant_result.state)) { - install_abort(relevant_result.state); - return false; - } - - install_target.fetched.add(key); - - if (prefix !== TYPE_PREFIX.BAG) - continue; - - for (const dependency of relevant_result.response.components) { - if (processed.has(dependency.join(''))) - continue; - needed.push(dependency); - } - } -} - -function record_fetched_install_dep(prefix, item, repo_url, result) -{ - const key = prefix + item; - - if (!install_target || repo_url !== install_target.repo_url || - !install_target.to_fetch.has(key)) - return; - - if (possible_errors.includes(result.state)) { - install_abort(result.state); - return; - } - - if (result.state !== "completed") - return; - - install_target.to_fetch.delete(key); - install_target.fetched.add(key); - - if (prefix === TYPE_PREFIX.BAG && - fetch_install_deps(result.response.components) === false) - return; - - install_check_ready(); -} - -function install_clicked(entry_object) -{ - import_frame.show_loading(); - - install_target = { - repo_url: entry_object.repo_url, - pattern: entry_object.match_object.pattern, - payload: entry_object.match_object.payload, - fetched: new Set(), - to_fetch: new Set() - }; - - fetch_install_deps([install_target.payload]); - - install_check_ready(); -} - -var max_query_result_id = 0; - -function show_query_successful_result(result_item, repo_url, result) -{ - if (result.length === 0) { - show_message(result_item, "No results :("); - return; - } - - const cloned_ul_template = clone_template("result_patterns_list"); - set_appended(result_item, cloned_ul_template.ul); - - for (const match of result) { - const entry_object = clone_template("query_match_li"); - - entry_object.pattern.textContent = match.pattern; - - cloned_ul_template.ul.appendChild(entry_object.li); - - if (!match.payload) { - entry_object.payload.textContent = "(none)"; - for (const key of ["chbx", "triangle", "unroll"]) - entry_object[key].remove(); - continue; - } - - entry_object.payload.textContent = nice_name(...match.payload); - - const install_cb = () => install_clicked(entry_object); - entry_object.btn.addEventListener("click", install_cb); - - const chbx_id = `query_result_${max_query_result_id++}`; - entry_object.chbx.id = chbx_id; - entry_object.lbl.setAttribute("for", chbx_id); - - entry_object.unroll_cb = () => unroll_chbx_first_checked(entry_object); - entry_object.chbx.addEventListener("change", entry_object.unroll_cb); - - entry_object.component_object = match.payload; - entry_object.match_object = match; - entry_object.repo_url = repo_url; - } -} - -function show_query_result(url_prefix, url, repo_url, result) -{ - const results_list_object = results_lists.get(url) || - create_results_list(url); - const result_item = results_list_object.by_repo.get(repo_url) || - create_result_item(results_list_object, repo_url, result); - - const completed_cb = - item => show_query_successful_result(item, repo_url, result.response); - const possible_actions = { - completed: completed_cb, - started: showcb("loading..."), - connection_error: showcb("Error when querying repository."), - parse_error: showcb("Bad data format received.") - }; - possible_actions[result.state](result_item, repo_url); -} - -by_id("settings_but") - .addEventListener("click", (e) => browser.runtime.openOptionsPage()); - -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; - show_page_activity_info(); -} - -main(); diff --git a/html/display_panel.html b/html/display_panel.html new file mode 100644 index 0000000..5ee20f1 --- /dev/null +++ b/html/display_panel.html @@ -0,0 +1,375 @@ + + + + + + Haketilo - page settings +#COPY_FILE html/reset.css + +#COPY_FILE html/base.css + +#COPY_FILE html/back_button.css + +#COPY_FILE html/table.css + +#IF MOZILLA +#COPY_FILE html/mozilla_scrollbar_fix.css + +#ENDIF + + + + + + +
+

Site modifiers install

+
+#INCLUDE html/import_frame.html +
+
+ + +
+

Injected scripts

+
+ None +
+
+ + +
+

Possible patterns for this page

+
+ +
+
+
+ + + +
+
+
+
+ +#INCLUDE html/default_blocking_policy.html + +
+
+ + +
+

Queried from repositories

+
+
+
+ + +
+

+

Privileged page

+ +
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + +
Matched pattern:... + +
Scripts blocked:...
Injected payload:... + +
+ +
+ This is a non-HTML page. Chosen payload will not be injected. +
+ +
+
+

+ Connecting to content script... +

+ +
+
+
+ + +
+ +
+ + +#LOADJS html/display_panel.js + + diff --git a/html/display_panel.js b/html/display_panel.js new file mode 100644 index 0000000..9d880ca --- /dev/null +++ b/html/display_panel.js @@ -0,0 +1,586 @@ +/** + * This file is part of Haketilo. + * + * Function: Popup logic. + * + * 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 . + * + * 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. + */ + +#IMPORT common/connection_types.js AS CONNECTION_TYPE + +#FROM common/browser.js IMPORT browser +/* + * 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 's reflow on stupid Firefox. */ +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"); + +const tab_query = {currentWindow: true, active: true}; + +async function get_current_tab() +{ +#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]; + } catch(e) { + console.log(e); + } +} + +const page_url_heading = by_id("page_url_heading"); +const privileged_notice = by_id("privileged_notice"); +const page_state = by_id("page_state"); + +/* Helper functions to convert string into a list of one-letter 's. */ +function char_to_span(char, doc) +{ + const span = document.createElement("span"); + span.textContent = char; + return span; +} + +function to_spans(string, doc=document) +{ + return string.split("").map(c => char_to_span(c, doc)); +} + +async function show_page_activity_info() +{ + const tab = await get_current_tab(); + + if (tab === undefined) { + page_url_heading.textContent = "unknown page"; + return; + } + + tab_url = /^([^?#]*)/.exec(tab.url)[1]; + to_spans(tab_url).forEach(s => page_url_heading.append(s)); + if (is_privileged_url(tab_url)) { + privileged_notice.classList.remove("hide"); + return; + } + + populate_possible_patterns_list(tab_url); + page_state.classList.remove("hide"); + + try_to_connect(tab.id); +} + +const possible_patterns_list = by_id("possible_patterns"); +const known_patterns = new Map(); + +function add_pattern_to_list(pattern) +{ + const template = clone_template("pattern_entry"); + template.name.textContent = pattern; + + const settings_opener = () => open_in_settings(TYPE_PREFIX.PAGE, pattern); + template.button.addEventListener("click", settings_opener); + + known_patterns.set(pattern, template); + possible_patterns_list.append(template.entry); + + return template; +} + +function style_possible_pattern_entry(pattern, exists_in_settings) +{ + const [text, class_action] = exists_in_settings ? + ["Edit", "add"] : ["Add", "remove"]; + const entry_object = known_patterns.get(pattern); + + if (entry_object) { + entry_object.button.textContent = `${text} setting`; + entry_object.entry.classList[class_action]("matched_pattern"); + } +} + +function handle_page_change(change) +{ + style_possible_pattern_entry(change.item, change.new_val !== undefined); +} + +function populate_possible_patterns_list(url) +{ + for (const pattern of each_url_pattern(url)) + add_pattern_to_list(pattern); + + for (const [pattern, settings] of query_all(storage, url)) + style_possible_pattern_entry(pattern, true); + + storage.add_change_listener(handle_page_change, [TYPE_PREFIX.PAGE]); +} + +const connected_chbx = by_id("connected_chbx"); +const query_pattern_but = by_id("query_pattern"); + +var content_script_port; + +function try_to_connect(tab_id) +{ + /* This won't connect to iframes. We'll add support for them later */ + const connect_info = {name: CONNECTION_TYPE.ACTIVITY_INFO, frameId: 0}; + content_script_port = browser.tabs.connect(tab_id, connect_info); + + const disconnect_cb = () => handle_disconnect(tab_id, start_querying_repos); + content_script_port.onDisconnect.addListener(disconnect_cb); + content_script_port.onMessage.addListener(handle_activity_report); + + query_pattern_but.addEventListener("click", start_querying_repos); + +#IF MOZILLA + setTimeout(() => monitor_connecting(tab_id), 1000); +#ENDIF +} + +function start_querying_repos() +{ + query_pattern_but.removeEventListener("click", start_querying_repos); + const repo_urls = storage.get_all_names(TYPE_PREFIX.REPO); + if (content_script_port) + content_script_port.postMessage([TYPE_PREFIX.URL, tab_url, repo_urls]); +} + +const loading_point = by_id("loading_point"); +const reload_notice = by_id("reload_notice"); + +function handle_disconnect(tab_id, button_cb) +{ + query_pattern_but.removeEventListener("click", button_cb); + content_script_port = null; + +#IF CHROMIUM + if (!browser.runtime.lastError) + return; +#ENDIF + + /* return if error was not during connection initialization */ + if (connected_chbx.checked) + return; + + loading_point.classList.toggle("camouflage"); + reload_notice.classList.remove("hide"); + + setTimeout(() => try_to_connect(tab_id), 1000); +} + +function monitor_connecting(tab_id) +{ + if (connected_chbx.checked) + return; + + if (content_script_port) + content_script_port.disconnect(); + else + return; + + loading_point.classList.toggle("camouflage"); + reload_notice.classList.remove("hide"); + try_to_connect(tab_id); +} + +const pattern_span = by_id("pattern"); +const view_pattern_but = by_id("view_pattern"); +const blocked_span = by_id("blocked"); +const payload_span = by_id("payload"); +const payload_buttons_div = by_id("payload_buttons"); +const view_payload_but = by_id("view_payload"); +const view_injected_but = by_id("view_injected"); +const container_for_injected = by_id("container_for_injected"); +const content_type_cell = by_id("content_type"); + +const queried_items = new Map(); + +let max_injected_script_id = 0; + +function handle_activity_report(message) +{ + connected_chbx.checked = true; + + const [type, data] = message; + + if (type === "settings") { + const settings = data; + + blocked_span.textContent = settings.allow ? "no" : "yes"; + + if (settings.pattern) { + pattern_span.textContent = settings.pattern; + const settings_opener = + () => open_in_settings(TYPE_PREFIX.PAGE, settings.pattern); + view_pattern_but.classList.remove("hide"); + view_pattern_but.addEventListener("click", settings_opener); + } else { + pattern_span.textContent = "none"; + blocked_span.textContent = blocked_span.textContent + " (default)"; + } + + if (settings.payload) { + payload_span.textContent = nice_name(...settings.payload); + payload_buttons_div.classList.remove("hide"); + const settings_opener = () => open_in_settings(...settings.payload); + view_payload_but.addEventListener("click", settings_opener); + } else { + payload_span.textContent = "none"; + } + } + if (type === "script") { + const template = clone_template("injected_script"); + const chbx_id = `injected_script_${max_injected_script_id++}`; + template.chbx.id = chbx_id; + template.lbl.setAttribute("for", chbx_id); + template.script_contents.textContent = data; + container_for_injected.appendChild(template.div); + } + if (type === "is_html") { + if (!data) + content_type_cell.classList.remove("hide"); + } + if (type === "repo_query_action") { + const key = data.prefix + data.item; + const results = queried_items.get(key) || {}; + Object.assign(results, data.results); + queried_items.set(key, results); + + const action = data.prefix === TYPE_PREFIX.URL ? + show_query_result : record_fetched_install_dep; + + for (const [repo_url, result] of Object.entries(data.results)) + action(data.prefix, data.item, repo_url, result); + } +} + +const container_for_repo_responses = by_id("container_for_repo_responses"); + +const results_lists = new Map(); + +function create_results_list(url) +{ + const cloned_template = clone_template("multi_repos_query_result"); + cloned_template.url_span.textContent = url; + container_for_repo_responses.appendChild(cloned_template.div); + + cloned_template.by_repo = new Map(); + results_lists.set(url, cloned_template); + + return cloned_template; +} + +function create_result_item(list_object, repo_url, result) +{ + const cloned_template = clone_template("single_repo_query_result"); + cloned_template.repo_url.textContent = repo_url; + cloned_template.appended = null; + + list_object.ul.appendChild(cloned_template.li); + list_object.by_repo.set(repo_url, cloned_template); + + return cloned_template; +} + +function set_appended(result_item, element) +{ + if (result_item.appended) + result_item.appended.remove(); + result_item.appended = element; + result_item.li.appendChild(element); +} + +function show_message(result_item, text) +{ + const div = document.createElement("div"); + div.textContent = text; + set_appended(result_item, div); +} + +function showcb(text) +{ + return item => show_message(item, text); +} + +function unroll_chbx_first_checked(entry_object) +{ + if (!entry_object.chbx.checked) + return; + + entry_object.chbx.removeEventListener("change", entry_object.unroll_cb); + delete entry_object.unroll_cb; + + entry_object.unroll.innerHTML = "preview not implemented...
(consider contributing)"; +} + +let import_frame; +let install_target = null; + +function install_abort(error_state) +{ + import_frame.show_error(`Error: ${error_state}`); + install_target = null; +} + +/* + * Translate objects from the format in which they are sent by Hydrilla to the + * format in which they are stored in settings. + */ + +function translate_script(script_object, repo_url) +{ + return { + [TYPE_PREFIX.SCRIPT + script_object.name]: { + hash: script_object.sha256, + url: `${repo_url}/content/${script_object.location}` + } + }; +} + +function translate_bag(bag_object) +{ + return { + [TYPE_PREFIX.BAG + bag_object.name]: bag_object.components + }; +} + +const format_translators = { + [TYPE_PREFIX.BAG]: translate_bag, + [TYPE_PREFIX.SCRIPT]: translate_script +}; + +function install_check_ready() +{ + if (install_target.to_fetch.size > 0) + return; + + const page_key = [TYPE_PREFIX.PAGE + install_target.pattern]; + const to_install = [{[page_key]: {components: install_target.payload}}]; + + for (const key of install_target.fetched) { + const old_object = + queried_items.get(key)[install_target.repo_url].response; + const new_object = + format_translators[key[0]](old_object, install_target.repo_url); + to_install.push(new_object); + } + + import_frame.show_selection(to_install); +} + +const possible_errors = ["connection_error", "parse_error"]; + +function fetch_install_deps(components) +{ + const needed = [...components]; + const processed = new Set(); + + while (needed.length > 0) { + const [prefix, item] = needed.pop(); + const key = prefix + item; + processed.add(key); + const results = queried_items.get(key); + let relevant_result = null; + + if (results) + relevant_result = results[install_target.repo_url]; + + if (!relevant_result) { + content_script_port.postMessage([prefix, item, + [install_target.repo_url]]); + install_target.to_fetch.add(key); + continue; + } + + if (possible_errors.includes(relevant_result.state)) { + install_abort(relevant_result.state); + return false; + } + + install_target.fetched.add(key); + + if (prefix !== TYPE_PREFIX.BAG) + continue; + + for (const dependency of relevant_result.response.components) { + if (processed.has(dependency.join(''))) + continue; + needed.push(dependency); + } + } +} + +function record_fetched_install_dep(prefix, item, repo_url, result) +{ + const key = prefix + item; + + if (!install_target || repo_url !== install_target.repo_url || + !install_target.to_fetch.has(key)) + return; + + if (possible_errors.includes(result.state)) { + install_abort(result.state); + return; + } + + if (result.state !== "completed") + return; + + install_target.to_fetch.delete(key); + install_target.fetched.add(key); + + if (prefix === TYPE_PREFIX.BAG && + fetch_install_deps(result.response.components) === false) + return; + + install_check_ready(); +} + +function install_clicked(entry_object) +{ + import_frame.show_loading(); + + install_target = { + repo_url: entry_object.repo_url, + pattern: entry_object.match_object.pattern, + payload: entry_object.match_object.payload, + fetched: new Set(), + to_fetch: new Set() + }; + + fetch_install_deps([install_target.payload]); + + install_check_ready(); +} + +var max_query_result_id = 0; + +function show_query_successful_result(result_item, repo_url, result) +{ + if (result.length === 0) { + show_message(result_item, "No results :("); + return; + } + + const cloned_ul_template = clone_template("result_patterns_list"); + set_appended(result_item, cloned_ul_template.ul); + + for (const match of result) { + const entry_object = clone_template("query_match_li"); + + entry_object.pattern.textContent = match.pattern; + + cloned_ul_template.ul.appendChild(entry_object.li); + + if (!match.payload) { + entry_object.payload.textContent = "(none)"; + for (const key of ["chbx", "triangle", "unroll"]) + entry_object[key].remove(); + continue; + } + + entry_object.payload.textContent = nice_name(...match.payload); + + const install_cb = () => install_clicked(entry_object); + entry_object.btn.addEventListener("click", install_cb); + + const chbx_id = `query_result_${max_query_result_id++}`; + entry_object.chbx.id = chbx_id; + entry_object.lbl.setAttribute("for", chbx_id); + + entry_object.unroll_cb = () => unroll_chbx_first_checked(entry_object); + entry_object.chbx.addEventListener("change", entry_object.unroll_cb); + + entry_object.component_object = match.payload; + entry_object.match_object = match; + entry_object.repo_url = repo_url; + } +} + +function show_query_result(url_prefix, url, repo_url, result) +{ + const results_list_object = results_lists.get(url) || + create_results_list(url); + const result_item = results_list_object.by_repo.get(repo_url) || + create_result_item(results_list_object, repo_url, result); + + const completed_cb = + item => show_query_successful_result(item, repo_url, result.response); + const possible_actions = { + completed: completed_cb, + started: showcb("loading..."), + connection_error: showcb("Error when querying repository."), + parse_error: showcb("Bad data format received.") + }; + possible_actions[result.state](result_item, repo_url); +} + +by_id("settings_but") + .addEventListener("click", (e) => browser.runtime.openOptionsPage()); + +async function main() +{ + storage = await get_remote_storage(); + import_frame = await get_import_frame(); + import_frame.onclose = () => show_queried_view_radio.checked = true; + show_page_activity_info(); +} + +main(); 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 @@ + + +