From 792fbe187bdffca4a748e88d66ea29f8936ae5c8 Mon Sep 17 00:00:00 2001 From: Wojtek Kosior Date: Fri, 6 Aug 2021 17:17:45 +0200 Subject: Facilitate installation of scripts from the repository This commit includes: * removal of page_info_server * running of storage client in popup context * extraction of some common CSS to a separate file * extraction of scripts import view to a separate file * addition of a facility to conveniently clone complex structures from DOM (in DOM_helpers.js) * addition of hydrilla repo url to default settings * other minor changes and of course changes related to the actual installation of scripts from the repo --- html/display-panel.js | 392 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 338 insertions(+), 54 deletions(-) (limited to 'html/display-panel.js') diff --git a/html/display-panel.js b/html/display-panel.js index 1693182..b4d9abb 100644 --- a/html/display-panel.js +++ b/html/display-panel.js @@ -10,6 +10,11 @@ * 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 query_all * IMPORT CONNECTION_TYPE * IMPORT url_item * IMPORT is_privileged_url @@ -17,13 +22,13 @@ * IMPORT nice_name * IMPORT open_in_settings * IMPORT for_each_possible_pattern + * IMPORT by_id + * IMPORT clone_template * IMPORTS_END */ -function by_id(id) -{ - return document.getElementById(id); -} +let storage; +let tab_url; const tab_query = {currentWindow: true, active: true}; @@ -55,28 +60,19 @@ async function show_page_activity_info() return; } - const url = url_item(tab.url); - page_url_heading.textContent = url; - if (is_privileged_url(url)) { + tab_url = url_item(tab.url); + page_url_heading.textContent = tab_url; + if (is_privileged_url(tab_url)) { show_privileged_notice_chbx.checked = true; return; } - populate_possible_patterns_list(url); + populate_possible_patterns_list(tab_url); show_page_state_chbx.checked = true; try_to_connect(tab.id); } -function populate_possible_patterns_list(url) -{ - for_each_possible_pattern(url, add_pattern_to_list); - - const port = browser.runtime.connect({name: CONNECTION_TYPE.PAGE_INFO}); - port.onMessage.addListener(handle_page_info); - port.postMessage(["subscribe", url]); -} - const possible_patterns_ul = by_id("possible_patterns"); const pattern_li_template = by_id("pattern_li_template"); pattern_li_template.removeAttribute("id"); @@ -121,53 +117,55 @@ function set_pattern_li_button_text(li_id, text) by_id(li_id).firstElementChild.nextElementSibling.textContent = text; } -function handle_page_info(message) +function handle_page_change(change) { - const [type, data] = message; + const li_id = ensure_pattern_exists(change.item); + if (change.old_val === undefined) + set_pattern_li_button_text(li_id, "Edit in settings"); + if (change.new_val === undefined) + set_pattern_li_button_text(li_id, "Add setting"); +} - if (type === "change") { - const li_id = ensure_pattern_exists(data.item); - if (data.old_val === undefined) - set_pattern_li_button_text(li_id, "Edit in settings"); - if (data.new_val === undefined) - set_pattern_li_button_text(li_id, "Add setting"); - } +function populate_possible_patterns_list(url) +{ + for_each_possible_pattern(url, add_pattern_to_list); - if (type === "new_url") { - for (const li_id of known_patterns.values()) - set_pattern_li_button_text(li_id, "Add setting"); - for (const [pattern, settings] of data) { - set_pattern_li_button_text(ensure_pattern_exists(pattern), - "Edit in settings") - } + for (const [pattern, settings] of query_all(storage, url)) { + set_pattern_li_button_text(ensure_pattern_exists(pattern), + "Edit in settings"); } + + 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}; - const port = browser.tabs.connect(tab_id, connect_info); + content_script_port = browser.tabs.connect(tab_id, connect_info); - const button_cb = (e) => start_querying_repos(port); + 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); - port.onDisconnect.addListener(port => handle_disconnect(tab_id, button_cb)); - port.onMessage.addListener(handle_activity_report); - - query_pattern_but.addEventListener("click", button_cb); + query_pattern_but.addEventListener("click", start_querying_repos); if (is_mozilla) - setTimeout(() => monitor_connecting(port, tab_id), 1000); + setTimeout(() => monitor_connecting(tab_id), 1000); } const query_started_chbx = by_id("query_started_chbx"); function start_querying_repos(port) { - port.postMessage("dummy (trigger repo querying)"); + 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]); query_started_chbx.checked = true; } @@ -176,6 +174,7 @@ const loading_chbx = by_id("loading_chbx"); 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; @@ -188,12 +187,16 @@ function handle_disconnect(tab_id, button_cb) setTimeout(() => try_to_connect(tab_id), 1000); } -function monitor_connecting(port, tab_id) +function monitor_connecting(tab_id) { if (connected_chbx.checked) return; - port.disconnect(); + if (content_script_port) + content_script_port.disconnect(); + else + return; + loading_chbx.checked = !loading_chbx.checked; try_to_connect(tab_id); } @@ -204,7 +207,8 @@ const blocked_span = by_id("blocked"); const payload_span = by_id("payload"); const view_payload_but = by_id("view_payload"); const container_for_injected = by_id("container_for_injected"); -const container_for_repo_responses = by_id("container_for_repo_responses"); + +const queried_items = new Map(); function handle_activity_report(message) { @@ -213,7 +217,7 @@ function handle_activity_report(message) const [type, data] = message; if (type === "settings") { - let [pattern, settings, repos] = data; + let [pattern, settings] = data; settings = settings || {}; blocked_span.textContent = settings.allow ? "no" : "yes"; @@ -247,20 +251,300 @@ function handle_activity_report(message) container_for_injected.appendChild(h4); container_for_injected.appendChild(pre); } - if (type === "repo_query_result") { - const [repo_url, response_text] = data; + if (type === "repo_query_action") { + query_started_chbx.checked = true; - const h4 = document.createElement("h4"); - const pre = document.createElement("pre"); - h4.textContent = repo_url; - pre.textContent = response_text; + 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 list_div = document.createElement("div"); + const list_head = document.createElement("h4"); + const list = document.createElement("ul"); + + list_head.textContent = url; + list_div.appendChild(list_head); + list_div.appendChild(list); + container_for_repo_responses.appendChild(list_div); + + const list_object = {list, by_repo: new Map()}; + + results_lists.set(url, list_object); + + return list_object; +} + +function create_result_item(list_object, repo_url, result) +{ + const result_li = document.createElement("li"); + const repo_url_span = document.createElement("span"); + const result_item = {result_li, appended: null}; + + repo_url_span.textContent = repo_url; + result_li.appendChild(repo_url_span); + + list_object.list.appendChild(result_li); + list_object.by_repo.set(repo_url, result_item); + + return result_item; +} + +function set_appended(result_item, element) +{ + if (result_item.appended) + result_item.appended.remove(); + result_item.appended = element; + result_item.result_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.textContent = "preview not implemented..."; +} + +const show_install_chbx = by_id("show_install_view_chbx"); + +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. + */ - container_for_repo_responses.appendChild(h4); - container_for_repo_responses.appendChild(pre); +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) +{ + show_install_chbx.checked = true; + 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) +{ + const ul = document.createElement("ul"); + + set_appended(result_item, ul); + + for (const match of result) { + const entry_object = clone_template("query_match_li_template"); + + entry_object.pattern.textContent = match.pattern; + + ul.appendChild(entry_object.li); + + if (!match.payload) { + entry_object.payload.textContent = "(none)"; + for (const key of ["chbx", "br", "triangle", "unroll"]) + entry_object[key].remove(); + continue; + } + + entry_object.component.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()); -show_page_activity_info(); +async function main() +{ + storage = await get_remote_storage(); + import_frame = await get_import_frame(); + import_frame.onclose = () => show_install_chbx.checked = false; + show_page_activity_info(); +} + +main(); -- cgit v1.2.3