diff options
-rw-r--r-- | background/main.js | 2 | ||||
-rw-r--r-- | background/page_actions_server.js | 16 | ||||
-rw-r--r-- | background/page_info_server.js | 77 | ||||
-rw-r--r-- | background/settings_query.js | 98 | ||||
-rw-r--r-- | common/connection_types.js | 4 | ||||
-rw-r--r-- | common/misc.js | 27 | ||||
-rw-r--r-- | common/patterns.js | 187 | ||||
-rw-r--r-- | content/activity_info_server.js | 60 | ||||
-rw-r--r-- | content/main.js | 54 | ||||
-rw-r--r-- | content/page_actions.js | 21 | ||||
-rw-r--r-- | html/display-panel.html | 85 | ||||
-rw-r--r-- | html/display-panel.js | 226 | ||||
-rw-r--r-- | html/options_main.js | 6 |
13 files changed, 735 insertions, 128 deletions
diff --git a/background/main.js b/background/main.js index 2fcdda3..da408f9 100644 --- a/background/main.js +++ b/background/main.js @@ -12,6 +12,7 @@ * IMPORT start_storage_server * IMPORT start_page_actions_server * IMPORT start_policy_injector + * IMPORT start_page_info_server * IMPORT browser * IMPORTS_END */ @@ -19,6 +20,7 @@ start_storage_server(); start_page_actions_server(); start_policy_injector(); +start_page_info_server(); async function init_myext(install_details) { diff --git a/background/page_actions_server.js b/background/page_actions_server.js index f9773f6..2d9c333 100644 --- a/background/page_actions_server.js +++ b/background/page_actions_server.js @@ -21,9 +21,12 @@ var storage; var query_best; var handler; -function send_scripts(url, port) +function send_actions(url, port) { let [pattern, settings] = query_best(url); + + port.postMessage(["settings", [pattern, settings]]); + if (settings === undefined) return; @@ -31,11 +34,11 @@ function send_scripts(url, port) let processed_bags = new Set(); if (components !== undefined) - send_scripts_rec([components], port, processed_bags); + send_scripts([components], port, processed_bags); } // TODO: parallelize script fetching -async function send_scripts_rec(components, port, processed_bags) +async function send_scripts(components, port, processed_bags) { for (let [prefix, name] of components) { if (prefix === TYPE_PREFIX.BAG) { @@ -52,14 +55,15 @@ async function send_scripts_rec(components, port, processed_bags) } processed_bags.add(name); - await send_scripts_rec(bag, port, processed_bags); + await send_scripts(bag, port, processed_bags); + processed_bags.delete(name); } else { let script_text = await get_script_text(name); if (script_text === undefined) continue; - port.postMessage({inject : [script_text]}); + port.postMessage(["inject", [script_text]]); } } } @@ -127,7 +131,7 @@ function handle_message(port, message, handler) port.onMessage.removeListener(handler[0]); let url = message.url; console.log({url}); - send_scripts(url, port); + send_actions(url, port); } function new_connection(port) diff --git a/background/page_info_server.js b/background/page_info_server.js new file mode 100644 index 0000000..49919fd --- /dev/null +++ b/background/page_info_server.js @@ -0,0 +1,77 @@ +/** + * part of Hachette + * Serving of storage data corresponding to requested urls (server side). + * + * Copyright (C) 2021 Wojtek Kosior + * Redistribution terms are gathered in the `copyright' file. + */ + +/* + * IMPORTS_START + * IMPORT listen_for_connection + * IMPORT get_storage + * IMPORT get_query_all + * IMPORT TYPE_PREFIX + * IMPORT CONNECTION_TYPE + * IMPORT url_matches + * IMPORTS_END + */ + +var storage; +var query_all; + +function handle_change(connection_data, change) +{ + if (change.prefix !== TYPE_PREFIX.PAGE) + return; + + connection_data.port.postMessage(["change", change]); +} + +async function handle_subscription(connection_data, message) +{ + const [action, url] = message; + if (action === "unsubscribe") { + connection_data.subscribed.delete(url); + return; + } + + connection_data.subscribed.add(url); + connection_data.port.postMessage(["new_url", query_all(url)]); +} + +function remove_storage_listener(cb) +{ + storage.remove_change_listener(cb); +} + +function new_connection(port) +{ + console.log("new page info connection!"); + + const connection_data = { + subscribed : new Set(), + port + }; + + let _handle_change = change => handle_change(connection_data, change); + + storage.add_change_listener(_handle_change); + + port.onMessage.addListener(m => handle_subscription(connection_data, m)); + port.onDisconnect.addListener(() => remove_storage_listener(handle_change)); +} + +async function start_page_info_server() +{ + storage = await get_storage(); + query_all = await get_query_all(); + + listen_for_connection(CONNECTION_TYPE.PAGE_INFO, new_connection); +} + +/* + * EXPORTS_START + * EXPORT start_page_info_server + * EXPORTS_END + */ diff --git a/background/settings_query.js b/background/settings_query.js index ce01b80..d509b09 100644 --- a/background/settings_query.js +++ b/background/settings_query.js @@ -10,13 +10,12 @@ * IMPORT make_once * IMPORT get_storage * IMPORT TYPE_PREFIX + * IMPORT for_each_possible_pattern * IMPORTS_END */ var storage; -var exports = {}; - async function init(fun) { storage = await get_storage(); @@ -24,94 +23,25 @@ async function init(fun) return fun; } -// TODO: also support urls with specified ports -function query(url, multiple) +function check_pattern(pattern, multiple, matched) { - let proto_re = "[a-zA-Z]*:\/\/"; - let domain_re = "[^/?#]+"; - let segments_re = "/[^?#]*"; - let query_re = "\\?[^#]*"; - - let url_regex = new RegExp(`\ -^\ -(${proto_re})\ -(${domain_re})\ -(${segments_re})?\ -(${query_re})?\ -#?.*\$\ -`); - - let regex_match = url_regex.exec(url); - if (regex_match === null) { - console.log("bad url format", url); - return multiple ? [] : [undefined, undefined]; - } - - let [_, proto, domain, segments, query] = regex_match; - - domain = domain.split("."); - let segments_trailing_dash = - segments && segments[segments.length - 1] === "/"; - segments = (segments || "").split("/").filter(s => s !== ""); - segments.unshift(""); - - let matched = []; + const settings = storage.get(TYPE_PREFIX.PAGE, pattern); - for (let d_slice = 0; d_slice < domain.length; d_slice++) { - let domain_part = domain.slice(d_slice).join("."); - let domain_wildcards = []; - if (d_slice === 0) - domain_wildcards.push(""); - if (d_slice === 1) - domain_wildcards.push("*."); - if (d_slice > 0) - domain_wildcards.push("**."); - domain_wildcards.push("***."); + if (settings === undefined) + return; - for (let domain_wildcard of domain_wildcards) { - let domain_pattern = domain_wildcard + domain_part; + matched.push([pattern, settings]); - for (let s_slice = segments.length; s_slice > 0; s_slice--) { - let segments_part = segments.slice(0, s_slice).join("/"); - let segments_wildcards = []; - if (s_slice === segments.length) { - if (segments_trailing_dash) - segments_wildcards.push("/"); - segments_wildcards.push(""); - } - if (s_slice === segments.length - 1) { - if (segments[s_slice] !== "*") - segments_wildcards.push("/*"); - } - if (s_slice < segments.length && - (segments[s_slice] !== "**" || - s_slice < segments.length - 1)) - segments_wildcards.push("/**"); - if (segments[s_slice] !== "***" || - s_slice < segments.length) - segments_wildcards.push("/***"); - - for (let segments_wildcard of segments_wildcards) { - let segments_pattern = - segments_part + segments_wildcard; - - let pattern = proto + domain_pattern + segments_pattern; - console.log("trying", pattern); - let settings = storage.get(TYPE_PREFIX.PAGE, pattern); - - if (settings === undefined) - continue; - - if (!multiple) - return [pattern, settings]; + if (!multiple) + return false; +} - matched.push([pattern, settings]); - } - } - } - } +function query(url, multiple) +{ + const matched = []; + for_each_possible_pattern(url, p => check_pattern(p, multiple, matched)); - return multiple ? matched : [undefined, undefined]; + return multiple ? matched : (matched[0] || [undefined, undefined]); } function query_best(url) diff --git a/common/connection_types.js b/common/connection_types.js index e227532..ce63e55 100644 --- a/common/connection_types.js +++ b/common/connection_types.js @@ -12,7 +12,9 @@ const CONNECTION_TYPE = { REMOTE_STORAGE : "0", - PAGE_ACTIONS : "1" + PAGE_ACTIONS : "1", + PAGE_INFO : "2", + ACTIVITY_INFO : "3" }; /* diff --git a/common/misc.js b/common/misc.js index 0a3a425..8b56e79 100644 --- a/common/misc.js +++ b/common/misc.js @@ -10,6 +10,7 @@ * IMPORT sha256 * IMPORT browser * IMPORT is_chrome + * IMPORT TYPE_NAME * IMPORTS_END */ @@ -72,10 +73,36 @@ function csp_rule(nonce) } /* + * Print item together with type, e.g. + * nice_name("s", "hello") → "hello (script)" + */ +function nice_name(prefix, name) +{ + return `${name} (${TYPE_NAME[prefix]})`; +} + +/* Open settings tab with given item's editing already on. */ +function open_in_settings(prefix, name) +{ + name = encodeURIComponent(name); + const url = browser.runtime.getURL("html/options.html#" + prefix + name); + window.open(url, "_blank"); +} + +/* Check if url corresponds to a browser's special page */ +function is_privileged_url(url) +{ + return !!/^(chrome(-extension)?|moz-extension):\/\/|^about:/i.exec(url); +} + +/* * EXPORTS_START * EXPORT gen_unique * EXPORT url_item * EXPORT url_extract_target * EXPORT csp_rule + * EXPORT nice_name + * EXPORT open_in_settings + * EXPORT is_privileged_url * EXPORTS_END */ diff --git a/common/patterns.js b/common/patterns.js new file mode 100644 index 0000000..f7da0ff --- /dev/null +++ b/common/patterns.js @@ -0,0 +1,187 @@ +/** + * Hydrilla/Lernette operations on page url patterns + * + * Copyright (C) 2021 Wojtek Kosior + * Redistribution terms are gathered in the `copyright' file. + */ + +const proto_re = "[a-zA-Z]*:\/\/"; +const domain_re = "[^/?#]+"; +const segments_re = "/[^?#]*"; +const query_re = "\\?[^#]*"; + +const url_regex = new RegExp(`\ +^\ +(${proto_re})\ +(${domain_re})\ +(${segments_re})?\ +(${query_re})?\ +#?.*\$\ +`); + +function deconstruct_url(url) +{ + const regex_match = url_regex.exec(url); + if (regex_match === null) + return undefined; + + let [_, proto, domain, path, query] = regex_match; + + domain = domain.split("."); + let path_trailing_dash = + path && path[path.length - 1] === "/"; + path = (path || "").split("/").filter(s => s !== ""); + path.unshift(""); + + return {proto, domain, path, query, path_trailing_dash}; +} + +/* Be sane: both arguments should be arrays of length >= 2 */ +function domain_matches(url_domain, pattern_domain) +{ + const length_difference = url_domain.length - pattern_domain.length; + + for (let i = 1; i <= url_domain.length; i++) { + const url_part = url_domain[url_domain.length - i]; + const pattern_part = pattern_domain[pattern_domain.length - i]; + + if (pattern_domain.length === i) { + if (pattern_part === "*") + return length_difference === 0; + if (pattern_part === "**") + return length_difference > 0; + if (pattern_part === "***") + return true; + return length_difference === 0 && pattern_part === url_part; + } + + if (pattern_part !== url_part) + return false; + } + + return pattern_domain.length === url_domain.length + 1 && + pattern_domain[0] === "***"; +} + +function path_matches(url_path, url_trailing_dash, + pattern_path, pattern_trailing_dash) +{ + const dashes_ok = !(pattern_trailing_dash && !url_trailing_dash); + + if (pattern_path.length === 0) + return url_path.length === 0 && dashes_ok; + + const length_difference = url_path.length - pattern_path.length; + + for (let i = 0; i < url_path.length; i++) { + if (pattern_path.length === i + 1) { + if (pattern_path[i] === "*") + return length_difference === 0; + if (pattern_path[i] === "**") { + return length_difference > 0 || + (url_path[i] === "**" && dashes_ok); + } + if (pattern_path[i] === "***") + return length_difference >= 0; + return length_difference === 0 && + pattern_path[i] === url_path[i] && dashes_ok; + } + + if (pattern_path[i] !== url_path[i]) + return false; + } + + return false; +} + +function url_matches(url, pattern) +{ + const url_deco = deconstruct_url(url); + const pattern_deco = deconstruct_url(pattern); + + if (url_deco === undefined || pattern_deco === undefined) { + console.log(`bad comparison: ${url} and ${pattern}`); + return false + } + + if (pattern_deco.proto !== url_deco.proto) + return false; + + return domain_matches(url_deco.domain, pattern_deco.domain) && + path_matches(url_deco.path, url_deco.path_trailing_dash, + pattern_deco.path, pattern_deco.path_trailing_dash); +} + +/* + * Call callback for every possible pattern that matches url. Return when there + * are no more patterns or callback returns false. + */ +function for_each_possible_pattern(url, callback) +{ + const deco = deconstruct_url(url); + + if (deco === undefined) { + console.log("bad url format", url); + return; + } + + for (let d_slice = 0; d_slice < deco.domain.length; d_slice++) { + const domain_part = deco.domain.slice(d_slice).join("."); + const domain_wildcards = []; + if (d_slice === 0) + domain_wildcards.push(""); + if (d_slice === 1) + domain_wildcards.push("*."); + if (d_slice > 0) + domain_wildcards.push("**."); + domain_wildcards.push("***."); + + for (const domain_wildcard of domain_wildcards) { + const domain_pattern = domain_wildcard + domain_part; + + for (let s_slice = deco.path.length; s_slice > 0; s_slice--) { + const path_part = deco.path.slice(0, s_slice).join("/"); + const path_wildcards = []; + if (s_slice === deco.path.length) { + if (deco.path_trailing_dash) + path_wildcards.push("/"); + path_wildcards.push(""); + } + if (s_slice === deco.path.length - 1 && + deco.path[s_slice] !== "*") + path_wildcards.push("/*"); + if (s_slice < deco.path.length && + (deco.path[s_slice] !== "**" || + s_slice < deco.path.length - 1)) + path_wildcards.push("/**"); + if (deco.path[s_slice] !== "***" || s_slice < deco.path.length) + path_wildcards.push("/***"); + + for (const path_wildcard of path_wildcards) { + const path_pattern = path_part + path_wildcard; + + const pattern = deco.proto + domain_pattern + path_pattern; + + if (callback(pattern) === false) + return; + } + } + } + } +} + +function possible_patterns(url) +{ + const patterns = []; + for_each_possible_pattern(url, patterns.push); + + return patterns; +} + +/* + * EXPORTS_START + * EXPORT url_matches + * EXPORT for_each_possible_pattern + * EXPORT possible_patterns + * EXPORTS_END + */ diff --git a/content/activity_info_server.js b/content/activity_info_server.js new file mode 100644 index 0000000..8435377 --- /dev/null +++ b/content/activity_info_server.js @@ -0,0 +1,60 @@ +/** + * part of Hachette + * Informing about activities performed by content script (script injection, + * script blocking). + * + * Copyright (C) 2021 Wojtek Kosior + * Redistribution terms are gathered in the `copyright' file. + */ + +/* + * IMPORTS_START + * IMPORT listen_for_connection + * IMPORT CONNECTION_TYPE + * IMPORTS_END + */ + +var activities = []; +var ports = new Set(); + +function report_activity(name, data) +{ + const activity = [name, data]; + activities.push(activity); + + for (const port of ports) + port.postMessage(activity); +} + +function report_script(script_data) +{ + report_activity("script", script_data); +} + +function report_settings(settings) +{ + report_activity("settings", settings); +} + +function new_connection(port) +{ + console.log("new activity info connection!"); + + ports.add(port); + + for (const activity of activities) + port.postMessage(activity); +} + +function start_activity_info_server() +{ + listen_for_connection(CONNECTION_TYPE.ACTIVITY_INFO, new_connection); +} + +/* + * EXPORTS_START + * EXPORT start_activity_info_server + * EXPORT report_script + * EXPORT report_settings + * EXPORTS_END + */ diff --git a/content/main.js b/content/main.js index 65ac008..5d88a6d 100644 --- a/content/main.js +++ b/content/main.js @@ -12,10 +12,12 @@ * IMPORT url_extract_target * IMPORT gen_unique * IMPORT csp_rule + * IMPORT is_privileged_url * IMPORT sanitize_attributes * IMPORT script_suppressor * IMPORT is_chrome * IMPORT is_mozilla + * IMPORT start_activity_info_server * IMPORTS_END */ @@ -35,11 +37,14 @@ let unique = gen_unique(url); const suppressor = script_suppressor(unique); -function needs_blocking() + +function is_http() { - if (url.startsWith("https://") || url.startsWith("http://")) - return false; + return !!/^https?:\/\//i.exec(document.URL); +} +function is_whitelisted() +{ const parsed_url = url_extract_target(document.URL); if (parsed_url.target !== undefined && @@ -49,12 +54,10 @@ function needs_blocking() else history.replaceState(null, "", parsed_url.base_url); - console.log(["allowing whitelisted", document.URL]); - return false; + return true; } - console.log(["disallowing", document.URL]); - return true; + return false; } function handle_mutation(mutations, observer) @@ -120,20 +123,27 @@ function inject_csp(head) head.insertBefore(meta, head.firstElementChild); } -if (needs_blocking()) { - block_nodes_recursively(document.documentElement); - - if (is_chrome) { - var observer = new MutationObserver(handle_mutation); - observer.observe(document.documentElement, { - attributes: true, - childList: true, - subtree: true - }); +if (!is_privileged_url(document.URL)) { + start_activity_info_server(); + handle_page_actions(unique); + + if (is_http()) { + /* rely on CSP injected through webRequest */ + } else if (is_whitelisted()) { + /* do not block scripts at all */ + } else { + block_nodes_recursively(document.documentElement); + + if (is_chrome) { + var observer = new MutationObserver(handle_mutation); + observer.observe(document.documentElement, { + attributes: true, + childList: true, + subtree: true + }); + } + + if (is_mozilla) + addEventListener('beforescriptexecute', suppressor, true); } - - if (is_mozilla) - addEventListener('beforescriptexecute', suppressor, true); } - -handle_page_actions(unique); diff --git a/content/page_actions.js b/content/page_actions.js index bc65449..fd405fe 100644 --- a/content/page_actions.js +++ b/content/page_actions.js @@ -9,6 +9,8 @@ * IMPORTS_START * IMPORT CONNECTION_TYPE * IMPORT browser + * IMPORT report_script + * IMPORT report_settings * IMPORTS_END */ @@ -19,15 +21,18 @@ var nonce; function handle_message(message) { - if (message.inject === undefined) - return; + const [action, data] = message; - for (let script_text of message.inject) { - if (loaded) - add_script(script_text); - else - scripts_awaiting.push(script_text); + if (action === "inject") { + for (let script_text of data) { + if (loaded) + add_script(script_text); + else + scripts_awaiting.push(script_text); + } } + if (action === "settings") + report_settings(data); } function document_loaded(event) @@ -46,6 +51,8 @@ function add_script(script_text) script.textContent = script_text; script.setAttribute("nonce", nonce); document.body.appendChild(script); + + report_script(script_text); } function handle_page_actions(script_nonce) { diff --git a/html/display-panel.html b/html/display-panel.html index 5e5580e..9b6d619 100644 --- a/html/display-panel.html +++ b/html/display-panel.html @@ -6,9 +6,90 @@ <html> <head> <meta charset="utf-8"/> - <title>Myext popup</title> + <title>Hachette - page settings</title> + <style> + input[type="radio"], input[type="checkbox"] { + display: none; + } + + body { + width: 300px; + height: 300px; + } + + .show_next:not(:checked)+* { + display: none; + } + + .hide { + display: none; + } + + #possible_patterns_chbx:not(:checked)+label span#triangle:first-child+span, + #possible_patterns_chbx:not(:checked)+label+*, + #possible_patterns_chbx:checked+label span#triangle:first-child { + display: none; + } + + #container_for_injected>#none_injected:not(:last-child) { + display: none; + } + + input#connected_chbx:checked+div+h3 { + display: none; + } + </style> </head> <body> - <button id="settings_but" type="button">Settings</button>_POPUPSCRIPTS_ + <!-- The invisible div below is for elements that will be cloned. --> + <div class="hide"> + <li id="pattern_li_template"> + <span></span> + <button>View in settings</button> + </li> + </div> + + <h2 id="page_url_heading"></h2> + + <input id="show_privileged_notice_chbx" type="checkbox" class="show_next"></input> + <h3>Privileged page</h3> + + <input id="show_page_state_chbx" type="checkbox" class="show_next"></input> + <div> + <input id="possible_patterns_chbx" type="checkbox"></input> + <label for="possible_patterns_chbx"> + <h3> + <span id="triangle">⏵</span><span>⏷</span> + Possible patterns + </h3> + </label> + <ul id="possible_patterns"></ul> + + <input id="connected_chbx" type="checkbox" class="show_next"></input> + <div> + <h3> + Matched pattern: <span id="pattern">...</span> + <button id="view_pattern" class="hide"> + View in settings + </button> + </h3> + <h3> + Blocked: <span id="blocked">...</span> + </h3> + <h3> + Payload: <span id="payload">...</span> + <button id="view_payload" class="hide"> + View in settings + </button> + </h3> + <h3>Injected</h3> + <div id="container_for_injected"> + <span id="none_injected">None</span> + </div> + </div> + <h3>Trying to connect..<input id="loading_chbx" type="checkbox" class="show_next"></input><span>.</span></h3> + </div> + + <button id="settings_but" type="button" style="margin-top: 20px;">Settings</button>_POPUPSCRIPTS_ </body> </html> diff --git a/html/display-panel.js b/html/display-panel.js index 4a4cdcd..9ae35fd 100644 --- a/html/display-panel.js +++ b/html/display-panel.js @@ -8,8 +8,232 @@ /* * IMPORTS_START * IMPORT browser + * IMPORT is_chrome + * IMPORT is_mozilla + * IMPORT CONNECTION_TYPE + * IMPORT url_item + * IMPORT is_privileged_url + * IMPORT TYPE_PREFIX + * IMPORT nice_name + * IMPORT open_in_settings + * IMPORT for_each_possible_pattern * IMPORTS_END */ -document.getElementById("settings_but") +function by_id(id) +{ + return document.getElementById(id); +} + +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 show_privileged_notice_chbx = by_id("show_privileged_notice_chbx"); +const show_page_state_chbx = by_id("show_page_state_chbx"); + +async function show_page_activity_info() +{ + const tab = await get_current_tab(); + + if (tab === undefined) { + page_url_heading.textContent = "unknown page"; + return; + } + + const url = url_item(tab.url); + page_url_heading.textContent = url; + if (is_privileged_url(url)) { + show_privileged_notice_chbx.checked = true; + return; + } + + populate_possible_patterns_list(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"); +const known_patterns = new Map(); + +function add_pattern_to_list(pattern) +{ + const li = pattern_li_template.cloneNode(true); + li.id = `pattern_li_${known_patterns.size}`; + known_patterns.set(pattern, li.id); + + const span = li.firstElementChild; + span.textContent = pattern; + + const button = span.nextElementSibling; + const settings_opener = () => open_in_settings(TYPE_PREFIX.PAGE, pattern); + button.addEventListener("click", settings_opener); + + possible_patterns_ul.appendChild(li) + + return li.id; +} + +function ensure_pattern_exists(pattern) +{ + let id = known_patterns.get(pattern); + /* + * As long as pattern computation works well, we should never get into this + * conditional block. This is just a safety measure. To be removed as part + * of a bigger rework when we start taking iframes into account. + */ + if (id === undefined) { + console.log(`unknown pattern: ${pattern}`); + id = add_pattern_to_list(pattern); + } + + return id; +} + +function set_pattern_li_button_text(li_id, text) +{ + by_id(li_id).firstElementChild.nextElementSibling.textContent = text; +} + +function handle_page_info(message) +{ + const [type, data] = message; + + 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"); + } + + 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") + } + } +} + +const connected_chbx = by_id("connected_chbx"); + +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); + + port.onDisconnect.addListener(port => handle_disconnect(tab_id)); + port.onMessage.addListener(handle_activity_report); + + if (is_mozilla) + setTimeout(() => monitor_connecting(port, tab_id), 1000); +} + +const loading_chbx = by_id("loading_chbx"); + +function handle_disconnect(tab_id) +{ + if (is_chrome && !browser.runtime.lastError) + return; + + /* return if there was no connection initialization failure */ + if (connected_chbx.checked) + return; + + loading_chbx.checked = !loading_chbx.checked; + setTimeout(() => try_to_connect(tab_id), 1000); +} + +function monitor_connecting(port, tab_id) +{ + if (connected_chbx.checked) + return; + + port.disconnect(); + loading_chbx.checked = !loading_chbx.checked; + 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 view_payload_but = by_id("view_payload"); +const container_for_injected = by_id("container_for_injected"); + +function handle_activity_report(message) +{ + connected_chbx.checked = true; + + const [type, data] = message; + + if (type === "settings") { + let [pattern, settings] = data; + + settings = settings || {}; + blocked_span.textContent = settings.allow ? "no" : "yes"; + + if (pattern) { + pattern_span.textContent = pattern; + const settings_opener = + () => open_in_settings(TYPE_PREFIX.PAGE, pattern); + view_pattern_but.classList.remove("hide"); + view_pattern_but.addEventListener("click", settings_opener); + } else { + pattern_span.textContent = "none"; + } + + const components = settings.components; + if (components) { + payload_span.textContent = nice_name(...components); + const settings_opener = () => open_in_settings(...components); + view_payload_but.classList.remove("hide"); + view_payload_but.addEventListener("click", settings_opener); + } else { + payload_span.textContent = "none"; + } + } + if (type === "script") { + const h4 = document.createElement("h4"); + const pre = document.createElement("pre"); + h4.textContent = "script"; + pre.textContent = data; + + container_for_injected.appendChild(h4); + container_for_injected.appendChild(pre); + } +} + +by_id("settings_but") .addEventListener("click", (e) => browser.runtime.openOptionsPage()); + +show_page_activity_info(); diff --git a/html/options_main.js b/html/options_main.js index 9c66a27..f7adf39 100644 --- a/html/options_main.js +++ b/html/options_main.js @@ -12,6 +12,7 @@ * IMPORT TYPE_NAME * IMPORT list_prefixes * IMPORT url_extract_target + * IMPORT nice_name * IMPORTS_END */ @@ -21,11 +22,6 @@ function by_id(id) return document.getElementById(id); } -function nice_name(prefix, name) -{ - return `${name} (${TYPE_NAME[prefix]})`; -} - const item_li_template = by_id("item_li_template"); const bag_component_li_template = by_id("bag_component_li_template"); const chbx_component_li_template = by_id("chbx_component_li_template"); |