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/display_panel.js | 586 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 586 insertions(+) create mode 100644 html/display_panel.js (limited to '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(); -- cgit v1.2.3