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