From b590eaa2f64ead3384eadc6fe58f6358aa1a0478 Mon Sep 17 00:00:00 2001 From: Wojtek Kosior Date: Wed, 22 Dec 2021 16:39:34 +0100 Subject: reworked build system; added missing license notices --- html/DOM_helpers.js | 15 +- html/MOZILLA_scrollbar_fix.css | 48 ---- html/back_button.css | 28 +- html/base.css | 29 +- html/default_blocking_policy.html | 32 ++- html/default_blocking_policy.js | 17 +- html/display-panel.html | 347 ---------------------- html/display-panel.js | 590 -------------------------------------- html/display_panel.html | 375 ++++++++++++++++++++++++ html/display_panel.js | 586 +++++++++++++++++++++++++++++++++++++ html/import_frame.html | 31 +- html/import_frame.js | 21 +- html/mozilla_scrollbar_fix.css | 67 +++++ html/options.html | 38 ++- html/options_main.js | 24 +- html/reset.css | 3 +- html/table.css | 29 ++ 17 files changed, 1216 insertions(+), 1064 deletions(-) delete mode 100644 html/MOZILLA_scrollbar_fix.css delete mode 100644 html/display-panel.html delete mode 100644 html/display-panel.js create mode 100644 html/display_panel.html create mode 100644 html/display_panel.js create mode 100644 html/mozilla_scrollbar_fix.css (limited to 'html') diff --git a/html/DOM_helpers.js b/html/DOM_helpers.js index aaf5621..443e4eb 100644 --- a/html/DOM_helpers.js +++ b/html/DOM_helpers.js @@ -41,10 +41,7 @@ * proprietary program, I am not going to enforce this in court. */ -function by_id(id) -{ - return document.getElementById(id); -} +#EXPORT id => document.getElementById(id) AS by_id const known_templates = new Map(); @@ -63,6 +60,7 @@ function get_template(template_id) known_templates.set(template_id, template); return template; } +#EXPORT get_template function clone_template(template_id) { @@ -86,11 +84,4 @@ function clone_template(template_id) return result_object; } - -/* - * EXPORTS_START - * EXPORT by_id - * EXPORT get_template - * EXPORT clone_template - * EXPORTS_END - */ +#EXPORT clone_template diff --git a/html/MOZILLA_scrollbar_fix.css b/html/MOZILLA_scrollbar_fix.css deleted file mode 100644 index cdbd5c6..0000000 --- a/html/MOZILLA_scrollbar_fix.css +++ /dev/null @@ -1,48 +0,0 @@ -/** - * This file is part of Haketilo. - * - * Function: Hacky fix for vertical scrollbar width being included in child's - * width. - * - * Copyright (C) 2021 Wojtek Kosior - * Redistribution terms are gathered in the `copyright' file. - */ - -/* - * Under Mozilla browsers to avoid vertical scrollbar forcing horizontal - * scrollbar to appear in an element we add the `firefox_scrollbars_hacky_fix' - * class to an element for which width has to be reserved. - * - * This is a bit hacky and relies on some assumed width of Firefox scrollbar, I - * know. And must be excluded from Chromium builds. - * - * I came up with this hack when working on popup. Before that I had the - * scrollbar issue with tables in the options page and gave up there and made - * the scrollbal always visible. Now we could try applying this "fix" there, - * too! - */ - -.firefox_scrollbars_hacky_fix { - font-size: 0; -} - -.firefox_scrollbars_hacky_fix>div { - display: inline-block; - width: -moz-available; -} - -.firefox_scrollbars_hacky_fix>*>* { - font-size: initial; -} - -.firefox_scrollbars_hacky_fix::after { - content: ""; - display: inline-block; - visibility: hidden; - font-size: initial; - width: 14px; -} - -.firefox_scrollbars_hacky_fix.has_inline_content::after { - width: calc(14px - 0.3em); -} diff --git a/html/back_button.css b/html/back_button.css index b83e834..94b18cf 100644 --- a/html/back_button.css +++ b/html/back_button.css @@ -1,10 +1,30 @@ -/** +/* + * SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0 + * + * Style for a "back" button with a CSS arrow image + * * This file is part of Haketilo. * - * Function: Style for a "back" button with a CSS arrow image. + * Copyright (C) 2021 Wojtek Kosior + * + * File is dual-licensed. You can choose either GPLv3+, CC BY-SA or both. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . * - * Copyright (C) 2021 Wojtek Kosior - * Redistribution terms are gathered in the `copyright' file. + * I, Wojtek Kosior, thereby promise not to sue for violation of this file's + * licenses. Although I request that you do not make use this code in a + * proprietary program, I am not going to enforce this in court. */ .back_button { diff --git a/html/base.css b/html/base.css index 517a5c1..47fc66b 100644 --- a/html/base.css +++ b/html/base.css @@ -1,11 +1,30 @@ -/** +/* + * SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0 + * + * Base styling common for all Haketilo internal HTML pages + * * This file is part of Haketilo. * - * Function: Base styles. + * Copyright (C) 2021 Wojtek Kosior + * + * File is dual-licensed. You can choose either GPLv3+, CC BY-SA or both. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . * - * Copyright (C) 2021 Wojtek Kosior - * Copyright (C) 2021 Nicholas Johnson - * Redistribution terms are gathered in the `copyright' file. + * I, Wojtek Kosior, thereby promise not to sue for violation of this file's + * licenses. Although I request that you do not make use this code in a + * proprietary program, I am not going to enforce this in court. */ body { diff --git a/html/default_blocking_policy.html b/html/default_blocking_policy.html index 50c19ca..547f756 100644 --- a/html/default_blocking_policy.html +++ b/html/default_blocking_policy.html @@ -1,10 +1,37 @@ + + + - - - - - -
-

Site modifiers install

-
- -
-
- - -
-

Injected scripts

-
- None -
-
- - -
-

Possible patterns for this page

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

Queried from repositories

-
-
-
- - -
-

-

Privileged page

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

- Connecting to content script... -

- -
-
-
- - -
- -
- - _POPUPSCRIPTS_ - - diff --git a/html/display-panel.js b/html/display-panel.js deleted file mode 100644 index d48d254..0000000 --- a/html/display-panel.js +++ /dev/null @@ -1,590 +0,0 @@ -/** - * This file is part of Haketilo. - * - * Function: Popup logic. - * - * Copyright (C) 2021 Wojtek Kosior - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * As additional permission under GNU GPL version 3 section 7, you - * may distribute forms of that code without the copy of the GNU - * GPL normally required by section 4, provided you include this - * license notice and, in case of non-source distribution, a URL - * through which recipients can access the Corresponding Source. - * If you modify file(s) with this exception, you may extend this - * exception to your version of the file(s), but you are not - * obligated to do so. If you do not wish to do so, delete this - * exception statement from your version. - * - * As a special exception to the GPL, any HTML file which merely - * makes function calls to this code, and for that purpose - * includes it by reference shall be deemed a separate work for - * copyright law purposes. If you modify this code, you may extend - * this exception to your version of the code, but you are not - * obligated to do so. If you do not wish to do so, delete this - * exception statement from your version. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * I, Wojtek Kosior, thereby promise not to sue for violation of this file's - * license. Although I request that you do not make use this code in a - * proprietary program, I am not going to enforce this in court. - */ - -/* - * IMPORTS_START - * IMPORT browser - * IMPORT is_chrome - * IMPORT is_mozilla - *** Using remote storage here seems inefficient, we only resort to that - *** temporarily, before all storage access gets reworked. - * IMPORT get_remote_storage - * IMPORT get_import_frame - * IMPORT init_default_policy_dialog - * IMPORT query_all - * IMPORT CONNECTION_TYPE - * IMPORT is_privileged_url - * IMPORT TYPE_PREFIX - * IMPORT nice_name - * IMPORT open_in_settings - * IMPORT each_url_pattern - * IMPORT by_id - * IMPORT clone_template - * IMPORTS_END - */ - -let storage; -let tab_url; - -/* Force popup 's reflow on stupid Firefox. */ -if (is_mozilla) { - const reflow_forcer = - () => document.documentElement.style.width = "-moz-fit-content"; - for (const radio of document.querySelectorAll('[name="current_view"]')) - radio.addEventListener("change", reflow_forcer); -} - -const show_queried_view_radio = by_id("show_queried_view_radio"); - -const tab_query = {currentWindow: true, active: true}; - -async function get_current_tab() -{ - /* Fix for fact that Chrome does not use promises here */ - const promise = is_chrome ? - new Promise((resolve, reject) => - browser.tabs.query(tab_query, tab => resolve(tab))) : - browser.tabs.query(tab_query); - - try { - return (await promise)[0]; - } catch(e) { - console.log(e); - } -} - -const page_url_heading = by_id("page_url_heading"); -const privileged_notice = by_id("privileged_notice"); -const page_state = by_id("page_state"); - -/* Helper functions to convert string into a list of one-letter 's. */ -function char_to_span(char, doc) -{ - const span = document.createElement("span"); - span.textContent = char; - return span; -} - -function to_spans(string, doc=document) -{ - return string.split("").map(c => char_to_span(c, doc)); -} - -async function show_page_activity_info() -{ - const tab = await get_current_tab(); - - if (tab === undefined) { - page_url_heading.textContent = "unknown page"; - return; - } - - tab_url = /^([^?#]*)/.exec(tab.url)[1]; - to_spans(tab_url).forEach(s => page_url_heading.append(s)); - if (is_privileged_url(tab_url)) { - privileged_notice.classList.remove("hide"); - return; - } - - populate_possible_patterns_list(tab_url); - page_state.classList.remove("hide"); - - try_to_connect(tab.id); -} - -const possible_patterns_list = by_id("possible_patterns"); -const known_patterns = new Map(); - -function add_pattern_to_list(pattern) -{ - const template = clone_template("pattern_entry"); - template.name.textContent = pattern; - - const settings_opener = () => open_in_settings(TYPE_PREFIX.PAGE, pattern); - template.button.addEventListener("click", settings_opener); - - known_patterns.set(pattern, template); - possible_patterns_list.append(template.entry); - - return template; -} - -function style_possible_pattern_entry(pattern, exists_in_settings) -{ - const [text, class_action] = exists_in_settings ? - ["Edit", "add"] : ["Add", "remove"]; - const entry_object = known_patterns.get(pattern); - - if (entry_object) { - entry_object.button.textContent = `${text} setting`; - entry_object.entry.classList[class_action]("matched_pattern"); - } -} - -function handle_page_change(change) -{ - style_possible_pattern_entry(change.item, change.new_val !== undefined); -} - -function populate_possible_patterns_list(url) -{ - for (const pattern of each_url_pattern(url)) - add_pattern_to_list(pattern); - - for (const [pattern, settings] of query_all(storage, url)) - style_possible_pattern_entry(pattern, true); - - storage.add_change_listener(handle_page_change, [TYPE_PREFIX.PAGE]); -} - -const connected_chbx = by_id("connected_chbx"); -const query_pattern_but = by_id("query_pattern"); - -var content_script_port; - -function try_to_connect(tab_id) -{ - /* This won't connect to iframes. We'll add support for them later */ - const connect_info = {name: CONNECTION_TYPE.ACTIVITY_INFO, frameId: 0}; - content_script_port = browser.tabs.connect(tab_id, connect_info); - - const disconnect_cb = () => handle_disconnect(tab_id, start_querying_repos); - content_script_port.onDisconnect.addListener(disconnect_cb); - content_script_port.onMessage.addListener(handle_activity_report); - - query_pattern_but.addEventListener("click", start_querying_repos); - - if (is_mozilla) - setTimeout(() => monitor_connecting(tab_id), 1000); -} - -function start_querying_repos() -{ - query_pattern_but.removeEventListener("click", start_querying_repos); - const repo_urls = storage.get_all_names(TYPE_PREFIX.REPO); - if (content_script_port) - content_script_port.postMessage([TYPE_PREFIX.URL, tab_url, repo_urls]); -} - -const loading_point = by_id("loading_point"); -const reload_notice = by_id("reload_notice"); - -function handle_disconnect(tab_id, button_cb) -{ - query_pattern_but.removeEventListener("click", button_cb); - content_script_port = null; - - if (is_chrome && !browser.runtime.lastError) - return; - - /* return if error was not during connection initialization */ - if (connected_chbx.checked) - return; - - loading_point.classList.toggle("camouflage"); - reload_notice.classList.remove("hide"); - - setTimeout(() => try_to_connect(tab_id), 1000); -} - -function monitor_connecting(tab_id) -{ - if (connected_chbx.checked) - return; - - if (content_script_port) - content_script_port.disconnect(); - else - return; - - loading_point.classList.toggle("camouflage"); - reload_notice.classList.remove("hide"); - try_to_connect(tab_id); -} - -const pattern_span = by_id("pattern"); -const view_pattern_but = by_id("view_pattern"); -const blocked_span = by_id("blocked"); -const payload_span = by_id("payload"); -const payload_buttons_div = by_id("payload_buttons"); -const view_payload_but = by_id("view_payload"); -const view_injected_but = by_id("view_injected"); -const container_for_injected = by_id("container_for_injected"); -const content_type_cell = by_id("content_type"); - -const queried_items = new Map(); - -let max_injected_script_id = 0; - -function handle_activity_report(message) -{ - connected_chbx.checked = true; - - const [type, data] = message; - - if (type === "settings") { - const settings = data; - - blocked_span.textContent = settings.allow ? "no" : "yes"; - - if (settings.pattern) { - pattern_span.textContent = pattern; - const settings_opener = - () => open_in_settings(TYPE_PREFIX.PAGE, settings.pattern); - view_pattern_but.classList.remove("hide"); - view_pattern_but.addEventListener("click", settings_opener); - } else { - pattern_span.textContent = "none"; - blocked_span.textContent = blocked_span.textContent + " (default)"; - } - - if (settings.payload) { - payload_span.textContent = nice_name(...settings.payload); - payload_buttons_div.classList.remove("hide"); - const settings_opener = () => open_in_settings(...settings.payload); - view_payload_but.addEventListener("click", settings_opener); - } else { - payload_span.textContent = "none"; - } - } - if (type === "script") { - const template = clone_template("injected_script"); - const chbx_id = `injected_script_${max_injected_script_id++}`; - template.chbx.id = chbx_id; - template.lbl.setAttribute("for", chbx_id); - template.script_contents.textContent = data; - container_for_injected.appendChild(template.div); - } - if (type === "is_html") { - if (!data) - content_type_cell.classList.remove("hide"); - } - if (type === "repo_query_action") { - const key = data.prefix + data.item; - const results = queried_items.get(key) || {}; - Object.assign(results, data.results); - queried_items.set(key, results); - - const action = data.prefix === TYPE_PREFIX.URL ? - show_query_result : record_fetched_install_dep; - - for (const [repo_url, result] of Object.entries(data.results)) - action(data.prefix, data.item, repo_url, result); - } -} - -const container_for_repo_responses = by_id("container_for_repo_responses"); - -const results_lists = new Map(); - -function create_results_list(url) -{ - const cloned_template = clone_template("multi_repos_query_result"); - cloned_template.url_span.textContent = url; - container_for_repo_responses.appendChild(cloned_template.div); - - cloned_template.by_repo = new Map(); - results_lists.set(url, cloned_template); - - return cloned_template; -} - -function create_result_item(list_object, repo_url, result) -{ - const cloned_template = clone_template("single_repo_query_result"); - cloned_template.repo_url.textContent = repo_url; - cloned_template.appended = null; - - list_object.ul.appendChild(cloned_template.li); - list_object.by_repo.set(repo_url, cloned_template); - - return cloned_template; -} - -function set_appended(result_item, element) -{ - if (result_item.appended) - result_item.appended.remove(); - result_item.appended = element; - result_item.li.appendChild(element); -} - -function show_message(result_item, text) -{ - const div = document.createElement("div"); - div.textContent = text; - set_appended(result_item, div); -} - -function showcb(text) -{ - return item => show_message(item, text); -} - -function unroll_chbx_first_checked(entry_object) -{ - if (!entry_object.chbx.checked) - return; - - entry_object.chbx.removeEventListener("change", entry_object.unroll_cb); - delete entry_object.unroll_cb; - - entry_object.unroll.innerHTML = "preview not implemented...
(consider contributing)"; -} - -let import_frame; -let install_target = null; - -function install_abort(error_state) -{ - import_frame.show_error(`Error: ${error_state}`); - install_target = null; -} - -/* - * Translate objects from the format in which they are sent by Hydrilla to the - * format in which they are stored in settings. - */ - -function translate_script(script_object, repo_url) -{ - return { - [TYPE_PREFIX.SCRIPT + script_object.name]: { - hash: script_object.sha256, - url: `${repo_url}/content/${script_object.location}` - } - }; -} - -function translate_bag(bag_object) -{ - return { - [TYPE_PREFIX.BAG + bag_object.name]: bag_object.components - }; -} - -const format_translators = { - [TYPE_PREFIX.BAG]: translate_bag, - [TYPE_PREFIX.SCRIPT]: translate_script -}; - -function install_check_ready() -{ - if (install_target.to_fetch.size > 0) - return; - - const page_key = [TYPE_PREFIX.PAGE + install_target.pattern]; - const to_install = [{[page_key]: {components: install_target.payload}}]; - - for (const key of install_target.fetched) { - const old_object = - queried_items.get(key)[install_target.repo_url].response; - const new_object = - format_translators[key[0]](old_object, install_target.repo_url); - to_install.push(new_object); - } - - import_frame.show_selection(to_install); -} - -const possible_errors = ["connection_error", "parse_error"]; - -function fetch_install_deps(components) -{ - const needed = [...components]; - const processed = new Set(); - - while (needed.length > 0) { - const [prefix, item] = needed.pop(); - const key = prefix + item; - processed.add(key); - const results = queried_items.get(key); - let relevant_result = null; - - if (results) - relevant_result = results[install_target.repo_url]; - - if (!relevant_result) { - content_script_port.postMessage([prefix, item, - [install_target.repo_url]]); - install_target.to_fetch.add(key); - continue; - } - - if (possible_errors.includes(relevant_result.state)) { - install_abort(relevant_result.state); - return false; - } - - install_target.fetched.add(key); - - if (prefix !== TYPE_PREFIX.BAG) - continue; - - for (const dependency of relevant_result.response.components) { - if (processed.has(dependency.join(''))) - continue; - needed.push(dependency); - } - } -} - -function record_fetched_install_dep(prefix, item, repo_url, result) -{ - const key = prefix + item; - - if (!install_target || repo_url !== install_target.repo_url || - !install_target.to_fetch.has(key)) - return; - - if (possible_errors.includes(result.state)) { - install_abort(result.state); - return; - } - - if (result.state !== "completed") - return; - - install_target.to_fetch.delete(key); - install_target.fetched.add(key); - - if (prefix === TYPE_PREFIX.BAG && - fetch_install_deps(result.response.components) === false) - return; - - install_check_ready(); -} - -function install_clicked(entry_object) -{ - import_frame.show_loading(); - - install_target = { - repo_url: entry_object.repo_url, - pattern: entry_object.match_object.pattern, - payload: entry_object.match_object.payload, - fetched: new Set(), - to_fetch: new Set() - }; - - fetch_install_deps([install_target.payload]); - - install_check_ready(); -} - -var max_query_result_id = 0; - -function show_query_successful_result(result_item, repo_url, result) -{ - if (result.length === 0) { - show_message(result_item, "No results :("); - return; - } - - const cloned_ul_template = clone_template("result_patterns_list"); - set_appended(result_item, cloned_ul_template.ul); - - for (const match of result) { - const entry_object = clone_template("query_match_li"); - - entry_object.pattern.textContent = match.pattern; - - cloned_ul_template.ul.appendChild(entry_object.li); - - if (!match.payload) { - entry_object.payload.textContent = "(none)"; - for (const key of ["chbx", "triangle", "unroll"]) - entry_object[key].remove(); - continue; - } - - entry_object.payload.textContent = nice_name(...match.payload); - - const install_cb = () => install_clicked(entry_object); - entry_object.btn.addEventListener("click", install_cb); - - const chbx_id = `query_result_${max_query_result_id++}`; - entry_object.chbx.id = chbx_id; - entry_object.lbl.setAttribute("for", chbx_id); - - entry_object.unroll_cb = () => unroll_chbx_first_checked(entry_object); - entry_object.chbx.addEventListener("change", entry_object.unroll_cb); - - entry_object.component_object = match.payload; - entry_object.match_object = match; - entry_object.repo_url = repo_url; - } -} - -function show_query_result(url_prefix, url, repo_url, result) -{ - const results_list_object = results_lists.get(url) || - create_results_list(url); - const result_item = results_list_object.by_repo.get(repo_url) || - create_result_item(results_list_object, repo_url, result); - - const completed_cb = - item => show_query_successful_result(item, repo_url, result.response); - const possible_actions = { - completed: completed_cb, - started: showcb("loading..."), - connection_error: showcb("Error when querying repository."), - parse_error: showcb("Bad data format received.") - }; - possible_actions[result.state](result_item, repo_url); -} - -by_id("settings_but") - .addEventListener("click", (e) => browser.runtime.openOptionsPage()); - -async function main() -{ - init_default_policy_dialog(); - - storage = await get_remote_storage(); - import_frame = await get_import_frame(); - import_frame.onclose = () => show_queried_view_radio.checked = true; - show_page_activity_info(); -} - -main(); diff --git a/html/display_panel.html b/html/display_panel.html new file mode 100644 index 0000000..5ee20f1 --- /dev/null +++ b/html/display_panel.html @@ -0,0 +1,375 @@ + + + + + + Haketilo - page settings +#COPY_FILE html/reset.css + +#COPY_FILE html/base.css + +#COPY_FILE html/back_button.css + +#COPY_FILE html/table.css + +#IF MOZILLA +#COPY_FILE html/mozilla_scrollbar_fix.css + +#ENDIF + + + + + + +
+

Site modifiers install

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

Injected scripts

+
+ None +
+
+ + +
+

Possible patterns for this page

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

Queried from repositories

+
+
+
+ + +
+

+

Privileged page

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

+ Connecting to content script... +

+ +
+
+
+ + +
+ +
+ + +#LOADJS html/display_panel.js + + diff --git a/html/display_panel.js b/html/display_panel.js new file mode 100644 index 0000000..9d880ca --- /dev/null +++ b/html/display_panel.js @@ -0,0 +1,586 @@ +/** + * This file is part of Haketilo. + * + * Function: Popup logic. + * + * Copyright (C) 2021 Wojtek Kosior + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * As additional permission under GNU GPL version 3 section 7, you + * may distribute forms of that code without the copy of the GNU + * GPL normally required by section 4, provided you include this + * license notice and, in case of non-source distribution, a URL + * through which recipients can access the Corresponding Source. + * If you modify file(s) with this exception, you may extend this + * exception to your version of the file(s), but you are not + * obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + * + * As a special exception to the GPL, any HTML file which merely + * makes function calls to this code, and for that purpose + * includes it by reference shall be deemed a separate work for + * copyright law purposes. If you modify this code, you may extend + * this exception to your version of the code, but you are not + * obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * I, Wojtek Kosior, thereby promise not to sue for violation of this file's + * license. Although I request that you do not make use this code in a + * proprietary program, I am not going to enforce this in court. + */ + +#IMPORT common/connection_types.js AS CONNECTION_TYPE + +#FROM common/browser.js IMPORT browser +/* + * Using remote storage here seems inefficient, we only resort to that + * temporarily, before all storage access gets reworked. + */ +#FROM common/storage_client.js IMPORT get_remote_storage +#FROM html/import_frame.js IMPORT get_import_frame +#FROM common/settings_query.js IMPORT query_all +#FROM common/misc.js IMPORT is_privileged_url, nice_name, \ + open_in_settings +#FROM common/stored_types.js IMPORT TYPE_PREFIX +#FROM common/patterns.js IMPORT each_url_pattern +#FROM html/DOM_helpers.js IMPORT by_id, clone_template + +let storage; +let tab_url; + +#IF MOZILLA +/* Force popup 's reflow on stupid Firefox. */ +const reflow_forcer = + () => document.documentElement.style.width = "-moz-fit-content"; +for (const radio of document.querySelectorAll('[name="current_view"]')) + radio.addEventListener("change", reflow_forcer); +#ENDIF + +const show_queried_view_radio = by_id("show_queried_view_radio"); + +const tab_query = {currentWindow: true, active: true}; + +async function get_current_tab() +{ +#IF CHROMIUM + const callback = (cb) => browser.tabs.query(tab_query, tab => cb(tab)); + const promise = new Promise(callback); +#ELIF MOZILLA + const promise = browser.tabs.query(tab_query); +#ENDIF + + try { + return (await promise)[0]; + } catch(e) { + console.log(e); + } +} + +const page_url_heading = by_id("page_url_heading"); +const privileged_notice = by_id("privileged_notice"); +const page_state = by_id("page_state"); + +/* Helper functions to convert string into a list of one-letter 's. */ +function char_to_span(char, doc) +{ + const span = document.createElement("span"); + span.textContent = char; + return span; +} + +function to_spans(string, doc=document) +{ + return string.split("").map(c => char_to_span(c, doc)); +} + +async function show_page_activity_info() +{ + const tab = await get_current_tab(); + + if (tab === undefined) { + page_url_heading.textContent = "unknown page"; + return; + } + + tab_url = /^([^?#]*)/.exec(tab.url)[1]; + to_spans(tab_url).forEach(s => page_url_heading.append(s)); + if (is_privileged_url(tab_url)) { + privileged_notice.classList.remove("hide"); + return; + } + + populate_possible_patterns_list(tab_url); + page_state.classList.remove("hide"); + + try_to_connect(tab.id); +} + +const possible_patterns_list = by_id("possible_patterns"); +const known_patterns = new Map(); + +function add_pattern_to_list(pattern) +{ + const template = clone_template("pattern_entry"); + template.name.textContent = pattern; + + const settings_opener = () => open_in_settings(TYPE_PREFIX.PAGE, pattern); + template.button.addEventListener("click", settings_opener); + + known_patterns.set(pattern, template); + possible_patterns_list.append(template.entry); + + return template; +} + +function style_possible_pattern_entry(pattern, exists_in_settings) +{ + const [text, class_action] = exists_in_settings ? + ["Edit", "add"] : ["Add", "remove"]; + const entry_object = known_patterns.get(pattern); + + if (entry_object) { + entry_object.button.textContent = `${text} setting`; + entry_object.entry.classList[class_action]("matched_pattern"); + } +} + +function handle_page_change(change) +{ + style_possible_pattern_entry(change.item, change.new_val !== undefined); +} + +function populate_possible_patterns_list(url) +{ + for (const pattern of each_url_pattern(url)) + add_pattern_to_list(pattern); + + for (const [pattern, settings] of query_all(storage, url)) + style_possible_pattern_entry(pattern, true); + + storage.add_change_listener(handle_page_change, [TYPE_PREFIX.PAGE]); +} + +const connected_chbx = by_id("connected_chbx"); +const query_pattern_but = by_id("query_pattern"); + +var content_script_port; + +function try_to_connect(tab_id) +{ + /* This won't connect to iframes. We'll add support for them later */ + const connect_info = {name: CONNECTION_TYPE.ACTIVITY_INFO, frameId: 0}; + content_script_port = browser.tabs.connect(tab_id, connect_info); + + const disconnect_cb = () => handle_disconnect(tab_id, start_querying_repos); + content_script_port.onDisconnect.addListener(disconnect_cb); + content_script_port.onMessage.addListener(handle_activity_report); + + query_pattern_but.addEventListener("click", start_querying_repos); + +#IF MOZILLA + setTimeout(() => monitor_connecting(tab_id), 1000); +#ENDIF +} + +function start_querying_repos() +{ + query_pattern_but.removeEventListener("click", start_querying_repos); + const repo_urls = storage.get_all_names(TYPE_PREFIX.REPO); + if (content_script_port) + content_script_port.postMessage([TYPE_PREFIX.URL, tab_url, repo_urls]); +} + +const loading_point = by_id("loading_point"); +const reload_notice = by_id("reload_notice"); + +function handle_disconnect(tab_id, button_cb) +{ + query_pattern_but.removeEventListener("click", button_cb); + content_script_port = null; + +#IF CHROMIUM + if (!browser.runtime.lastError) + return; +#ENDIF + + /* return if error was not during connection initialization */ + if (connected_chbx.checked) + return; + + loading_point.classList.toggle("camouflage"); + reload_notice.classList.remove("hide"); + + setTimeout(() => try_to_connect(tab_id), 1000); +} + +function monitor_connecting(tab_id) +{ + if (connected_chbx.checked) + return; + + if (content_script_port) + content_script_port.disconnect(); + else + return; + + loading_point.classList.toggle("camouflage"); + reload_notice.classList.remove("hide"); + try_to_connect(tab_id); +} + +const pattern_span = by_id("pattern"); +const view_pattern_but = by_id("view_pattern"); +const blocked_span = by_id("blocked"); +const payload_span = by_id("payload"); +const payload_buttons_div = by_id("payload_buttons"); +const view_payload_but = by_id("view_payload"); +const view_injected_but = by_id("view_injected"); +const container_for_injected = by_id("container_for_injected"); +const content_type_cell = by_id("content_type"); + +const queried_items = new Map(); + +let max_injected_script_id = 0; + +function handle_activity_report(message) +{ + connected_chbx.checked = true; + + const [type, data] = message; + + if (type === "settings") { + const settings = data; + + blocked_span.textContent = settings.allow ? "no" : "yes"; + + if (settings.pattern) { + pattern_span.textContent = settings.pattern; + const settings_opener = + () => open_in_settings(TYPE_PREFIX.PAGE, settings.pattern); + view_pattern_but.classList.remove("hide"); + view_pattern_but.addEventListener("click", settings_opener); + } else { + pattern_span.textContent = "none"; + blocked_span.textContent = blocked_span.textContent + " (default)"; + } + + if (settings.payload) { + payload_span.textContent = nice_name(...settings.payload); + payload_buttons_div.classList.remove("hide"); + const settings_opener = () => open_in_settings(...settings.payload); + view_payload_but.addEventListener("click", settings_opener); + } else { + payload_span.textContent = "none"; + } + } + if (type === "script") { + const template = clone_template("injected_script"); + const chbx_id = `injected_script_${max_injected_script_id++}`; + template.chbx.id = chbx_id; + template.lbl.setAttribute("for", chbx_id); + template.script_contents.textContent = data; + container_for_injected.appendChild(template.div); + } + if (type === "is_html") { + if (!data) + content_type_cell.classList.remove("hide"); + } + if (type === "repo_query_action") { + const key = data.prefix + data.item; + const results = queried_items.get(key) || {}; + Object.assign(results, data.results); + queried_items.set(key, results); + + const action = data.prefix === TYPE_PREFIX.URL ? + show_query_result : record_fetched_install_dep; + + for (const [repo_url, result] of Object.entries(data.results)) + action(data.prefix, data.item, repo_url, result); + } +} + +const container_for_repo_responses = by_id("container_for_repo_responses"); + +const results_lists = new Map(); + +function create_results_list(url) +{ + const cloned_template = clone_template("multi_repos_query_result"); + cloned_template.url_span.textContent = url; + container_for_repo_responses.appendChild(cloned_template.div); + + cloned_template.by_repo = new Map(); + results_lists.set(url, cloned_template); + + return cloned_template; +} + +function create_result_item(list_object, repo_url, result) +{ + const cloned_template = clone_template("single_repo_query_result"); + cloned_template.repo_url.textContent = repo_url; + cloned_template.appended = null; + + list_object.ul.appendChild(cloned_template.li); + list_object.by_repo.set(repo_url, cloned_template); + + return cloned_template; +} + +function set_appended(result_item, element) +{ + if (result_item.appended) + result_item.appended.remove(); + result_item.appended = element; + result_item.li.appendChild(element); +} + +function show_message(result_item, text) +{ + const div = document.createElement("div"); + div.textContent = text; + set_appended(result_item, div); +} + +function showcb(text) +{ + return item => show_message(item, text); +} + +function unroll_chbx_first_checked(entry_object) +{ + if (!entry_object.chbx.checked) + return; + + entry_object.chbx.removeEventListener("change", entry_object.unroll_cb); + delete entry_object.unroll_cb; + + entry_object.unroll.innerHTML = "preview not implemented...
(consider contributing)"; +} + +let import_frame; +let install_target = null; + +function install_abort(error_state) +{ + import_frame.show_error(`Error: ${error_state}`); + install_target = null; +} + +/* + * Translate objects from the format in which they are sent by Hydrilla to the + * format in which they are stored in settings. + */ + +function translate_script(script_object, repo_url) +{ + return { + [TYPE_PREFIX.SCRIPT + script_object.name]: { + hash: script_object.sha256, + url: `${repo_url}/content/${script_object.location}` + } + }; +} + +function translate_bag(bag_object) +{ + return { + [TYPE_PREFIX.BAG + bag_object.name]: bag_object.components + }; +} + +const format_translators = { + [TYPE_PREFIX.BAG]: translate_bag, + [TYPE_PREFIX.SCRIPT]: translate_script +}; + +function install_check_ready() +{ + if (install_target.to_fetch.size > 0) + return; + + const page_key = [TYPE_PREFIX.PAGE + install_target.pattern]; + const to_install = [{[page_key]: {components: install_target.payload}}]; + + for (const key of install_target.fetched) { + const old_object = + queried_items.get(key)[install_target.repo_url].response; + const new_object = + format_translators[key[0]](old_object, install_target.repo_url); + to_install.push(new_object); + } + + import_frame.show_selection(to_install); +} + +const possible_errors = ["connection_error", "parse_error"]; + +function fetch_install_deps(components) +{ + const needed = [...components]; + const processed = new Set(); + + while (needed.length > 0) { + const [prefix, item] = needed.pop(); + const key = prefix + item; + processed.add(key); + const results = queried_items.get(key); + let relevant_result = null; + + if (results) + relevant_result = results[install_target.repo_url]; + + if (!relevant_result) { + content_script_port.postMessage([prefix, item, + [install_target.repo_url]]); + install_target.to_fetch.add(key); + continue; + } + + if (possible_errors.includes(relevant_result.state)) { + install_abort(relevant_result.state); + return false; + } + + install_target.fetched.add(key); + + if (prefix !== TYPE_PREFIX.BAG) + continue; + + for (const dependency of relevant_result.response.components) { + if (processed.has(dependency.join(''))) + continue; + needed.push(dependency); + } + } +} + +function record_fetched_install_dep(prefix, item, repo_url, result) +{ + const key = prefix + item; + + if (!install_target || repo_url !== install_target.repo_url || + !install_target.to_fetch.has(key)) + return; + + if (possible_errors.includes(result.state)) { + install_abort(result.state); + return; + } + + if (result.state !== "completed") + return; + + install_target.to_fetch.delete(key); + install_target.fetched.add(key); + + if (prefix === TYPE_PREFIX.BAG && + fetch_install_deps(result.response.components) === false) + return; + + install_check_ready(); +} + +function install_clicked(entry_object) +{ + import_frame.show_loading(); + + install_target = { + repo_url: entry_object.repo_url, + pattern: entry_object.match_object.pattern, + payload: entry_object.match_object.payload, + fetched: new Set(), + to_fetch: new Set() + }; + + fetch_install_deps([install_target.payload]); + + install_check_ready(); +} + +var max_query_result_id = 0; + +function show_query_successful_result(result_item, repo_url, result) +{ + if (result.length === 0) { + show_message(result_item, "No results :("); + return; + } + + const cloned_ul_template = clone_template("result_patterns_list"); + set_appended(result_item, cloned_ul_template.ul); + + for (const match of result) { + const entry_object = clone_template("query_match_li"); + + entry_object.pattern.textContent = match.pattern; + + cloned_ul_template.ul.appendChild(entry_object.li); + + if (!match.payload) { + entry_object.payload.textContent = "(none)"; + for (const key of ["chbx", "triangle", "unroll"]) + entry_object[key].remove(); + continue; + } + + entry_object.payload.textContent = nice_name(...match.payload); + + const install_cb = () => install_clicked(entry_object); + entry_object.btn.addEventListener("click", install_cb); + + const chbx_id = `query_result_${max_query_result_id++}`; + entry_object.chbx.id = chbx_id; + entry_object.lbl.setAttribute("for", chbx_id); + + entry_object.unroll_cb = () => unroll_chbx_first_checked(entry_object); + entry_object.chbx.addEventListener("change", entry_object.unroll_cb); + + entry_object.component_object = match.payload; + entry_object.match_object = match; + entry_object.repo_url = repo_url; + } +} + +function show_query_result(url_prefix, url, repo_url, result) +{ + const results_list_object = results_lists.get(url) || + create_results_list(url); + const result_item = results_list_object.by_repo.get(repo_url) || + create_result_item(results_list_object, repo_url, result); + + const completed_cb = + item => show_query_successful_result(item, repo_url, result.response); + const possible_actions = { + completed: completed_cb, + started: showcb("loading..."), + connection_error: showcb("Error when querying repository."), + parse_error: showcb("Bad data format received.") + }; + possible_actions[result.state](result_item, repo_url); +} + +by_id("settings_but") + .addEventListener("click", (e) => browser.runtime.openOptionsPage()); + +async function main() +{ + storage = await get_remote_storage(); + import_frame = await get_import_frame(); + import_frame.onclose = () => show_queried_view_radio.checked = true; + show_page_activity_info(); +} + +main(); diff --git a/html/import_frame.html b/html/import_frame.html index 754b289..5b1f875 100644 --- a/html/import_frame.html +++ b/html/import_frame.html @@ -1,10 +1,37 @@ + + +