diff options
author | Ruben Rodriguez <ruben@trisquel.info> | 2022-09-08 20:18:54 -0400 |
---|---|---|
committer | Ruben Rodriguez <ruben@trisquel.info> | 2022-09-08 20:18:54 -0400 |
commit | 5da28b0f8771834ae208d61431d632875e9f8e7d (patch) | |
tree | 688ecaff26197bad8abde617b4947b11d617309e /data/extensions/jsr@javascriptrestrictor/nscl/service/DocStartInjection.js | |
parent | 4a87716686104266a9cccc2d83cc249e312f3673 (diff) |
Updated extensions:
* Upgraded Privacy Redirect to 1.1.49 and configured to use the 10 most reliable invidious instances
* Removed ViewTube
* Added torproxy@icecat.gnu based on 'Proxy toggle' extension
* Added jShelter 0.11.1
* Upgraded LibreJS to 7.21.0
* Upgraded HTTPS Everywhere to 2021.7.13
* Upgraded SubmitMe to 1.9
Diffstat (limited to 'data/extensions/jsr@javascriptrestrictor/nscl/service/DocStartInjection.js')
-rw-r--r-- | data/extensions/jsr@javascriptrestrictor/nscl/service/DocStartInjection.js | 221 |
1 files changed, 221 insertions, 0 deletions
diff --git a/data/extensions/jsr@javascriptrestrictor/nscl/service/DocStartInjection.js b/data/extensions/jsr@javascriptrestrictor/nscl/service/DocStartInjection.js new file mode 100644 index 0000000..aae8e38 --- /dev/null +++ b/data/extensions/jsr@javascriptrestrictor/nscl/service/DocStartInjection.js @@ -0,0 +1,221 @@ +/* + * NoScript Commons Library + * Reusable building blocks for cross-browser security/privacy WebExtensions. + * Copyright (C) 2020-2021 Giorgio Maone <https://maone.net> + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <https://www.gnu.org/licenses/>. + */ + +// depends on /nscl/lib/sha256.js +// depends on /nscl/common/uuid.js + +"use strict"; + +var DocStartInjection = (() => { + const MSG_ID = "__DocStartInjection__"; + let repeating = !("contentScripts" in browser); + let scriptBuilders = new Set(); + let getId = ({requestId, tabId, frameId, url}) => requestId || `${tabId}:${frameId}:${url}`; + let pending = new Map(); + + function onMessage(msg, sender) { + let payload = msg[MSG_ID]; + if (!payload) return; + let {id, tabId, frameId, url} = payload; + let ret = false; + if (tabId === sender.tab.id && frameId === sender.frameId && url === sender.url) { + end(payload, true); + ret = true; + } + return Promise.resolve(ret); + } + + async function begin(request) { + let scripts = new Set(); + let {tabId, frameId, url} = request; + if (tabId < 0 || !/^(?:(?:https?|ftp|data|blob|file):|about:blank$)/.test(url)) return; + + await Promise.all([...scriptBuilders].map(async buildScript => { + try { + let script = await buildScript({tabId, frameId, url}); + if (script) scripts.add(`try { + ${typeof script === "function" ? `(${script})();` : script} + } catch (e) { + console.error("Error in DocStartInjection script", e); + }`); + } catch (e) { + error("Error calling DocStartInjection scriptBuilder", buildScript, e); + } + })); + + if (scripts.size === 0) { + debug(`DocStartInjection: no script to inject in ${url}`); + return; + } + + let id = getId(request); + + if (repeating) { + let scriptsBlock = [...scripts].join("\n"); + let injectionId = `injection:${uuid()}:${sha256(scriptsBlock)}`; + let args = { + code: `(() => { + let injectionId = ${JSON.stringify(injectionId)}; + if (document.readyState === "complete" || + window[injectionId] || + document.URL !== ${JSON.stringify(url)} + ) return window[injectionId]; + window[injectionId] = true; + ${scriptsBlock} + return document.readyState === "loading"; + })();`, + runAt: "document_start", + frameId, + }; + pending.set(id, args); + await run(request, true); + } else { + let matches = [url]; + try { + let urlObj = new URL(url); + if (urlObj.port) { + urlObj.port = ""; + matches[0] = urlObj.toString(); + } + } catch (e) {} + + let ackMsg = JSON.stringify({ + [MSG_ID]: {id, tabId, frameId, url} + }); + scripts.add(`if (document.readyState !== "complete") browser.runtime.sendMessage(${ackMsg});`); + + let options = { + js: [...scripts].map(code => ({code})), + runAt: "document_start", + matchAboutBlank: true, + matches, + allFrames: true, + }; + let current = pending.get(id); + if (current) { + current.unregister(); + } + pending.set(id, await browser.contentScripts.register(options)); + } + } + + async function run(request, repeat = false) { + let id = getId(request); + let args = pending.get(id); + if (!args) return; + let {url, tabId} = request; + let attempts = 0, success = false; + for (; pending.has(id);) { + attempts++; + try { + if (attempts % 1000 === 0) { + let tab = await browser.tabs.get(request.tabId); + if (tab.url !== url) { + console.error(`Tab mismatch: ${tab.url} <> ${url} (download-triggered?)`); + break; + } + console.error(`DocStartInjection at ${url} ${attempts} failed attempts so far...`); + } + let ret = await browser.tabs.executeScript(tabId, args); + if (success = ret[0]) { + break; + } + } catch (e) { + if (/No tab\b/.test(e.message)) { + break; + } + if (!/\baccess\b/.test(e.message)) { + console.error(e.message); + } + if (attempts % 1000 === 0) { + console.error(`DocStartInjection at ${url} ${attempts} failed attempts`, e); + } + } finally { + if (!repeat) break; + } + } + pending.delete(id); + debug(`DocStartInjection at ${url}, ${attempts} attempts, success = ${success}, repeat = ${repeat}.`); + } + + function end(request, immediate = false) { + let id = getId(request); + let script = pending.get(id); + if (script) { + if (repeating) { + run(request, false); + } else { + pending.delete(id); + if (immediate) { + script.unregister(); + } else { + setTimeout(() => script.unregister(), 500); + } + } + } + } + + let listeners = { + onBeforeNavigate: begin, + onErrorOccurred: end, + onCompleted: end, + } + + function listen(enabled) { + let {webNavigation, webRequest} = browser; + let method = `${enabled ? "add" : "remove"}Listener`; + let reqFilter = {urls: ["<all_urls>"], types: ["main_frame", "sub_frame", "object"]}; + function setup(api, eventName, listener, ...args) { + let event = api[eventName]; + if (event) { + event[method].apply(event, enabled ? [listener, ...args] : [listener]); + } + } + if (repeating) { + // Just Chromium + setup(webRequest, "onResponseStarted", begin, reqFilter); + } else { + // add or remove Firefox's webNavigation listeners for non-http loads + // and asynchronous blocking onHeadersReceived for registration on http + let navFilter = enabled && {url: [{schemes: ["file", "ftp"]}]}; + for (let [eventName, listener] of Object.entries(listeners)) { + setup(webNavigation, eventName, listener, navFilter) + } + setup(webRequest, "onHeadersReceived", begin, reqFilter, ["blocking"]); + browser.runtime.onMessage[method](onMessage); + } + + // add or remove common webRequest listener + for (let [eventName, listener] of Object.entries(listeners)) { + setup(webRequest, eventName, listener, reqFilter); + } + } + + return { + register(scriptBuilder) { + if (scriptBuilders.size === 0) listen(true); + scriptBuilders.add(scriptBuilder); + }, + unregister(scriptBuilder) { + scriptBuilders.delete(scriptBuilder); + if (scriptBuilders.size() === 0) listen(false); + } + }; +})();
\ No newline at end of file |