diff options
Diffstat (limited to 'data/extensions/jsr@javascriptrestrictor/nscl/service/DocStartInjection.js')
-rw-r--r-- | data/extensions/jsr@javascriptrestrictor/nscl/service/DocStartInjection.js | 287 |
1 files changed, 0 insertions, 287 deletions
diff --git a/data/extensions/jsr@javascriptrestrictor/nscl/service/DocStartInjection.js b/data/extensions/jsr@javascriptrestrictor/nscl/service/DocStartInjection.js deleted file mode 100644 index 65571ab..0000000 --- a/data/extensions/jsr@javascriptrestrictor/nscl/service/DocStartInjection.js +++ /dev/null @@ -1,287 +0,0 @@ -/* - * NoScript Commons Library - * Reusable building blocks for cross-browser security/privacy WebExtensions. - * Copyright (C) 2020-2024 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/common/sha256.js -// depends on /nscl/common/uuid.js - -"use strict"; - -var DocStartInjection = (() => { - const MSG_ID = "__DocStartInjection__"; - const repeating = !("contentScripts" in browser); - const mv3Callbacks = repeating && !browser.tabs.executeScript; // mv3 on Chrome - - 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.allSettled([...scriptBuilders].map(async buildScript => { - let script; - try { - script = await buildScript({tabId, frameId, url}); - if (!script) return; - if (mv3Callbacks) { - if (typeof script !== "object") { - throw new Error('On MV3 only {data: jsonObject, callback: "globalFunctionName", assign: "globalScopeVarName"} injection can work!') - } - const {data, callback, assign} = script; - scripts.add({ - data, - callback, - assign, - }); - return; - } - // mv2 - 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 ${buildScript} - script: ${script}`, e); - } - })); - - if (scripts.size === 0) { - debug(`DocStartInjection: no script to inject in ${url}`); - return; - } - - let id = getId(request); - - if (repeating) { - let injectionId = `injection:${uuid()}:${await sha256(Math.random().toString(16))}`; - let args = mv3Callbacks ? - // mv3 browser.scripting.executeScript() - { - func: (url, injectionId, scripts) => { - if (document.readyState === "complete" || - window[injectionId] || - document.URL !== url - ) return window[injectionId]; - window[injectionId] = true; - for (s of scripts) { - const {callback, assign, data} = s; - try { - if (assign && !(assign in globalThis)) { - globalThis[assign] = data; - } - if (callback) { - let cb = globalThis[callback]; - if (typeof cb == "function") { - cb.call(globalThis, data); - } else { - console.warn(`callback globalThis.${callback} is not a function (${cb}).`); - } - } - } catch (e) { - console.error(`Error in DocStartInjection script ${JSON.stringify(s)}`, e); - } - } - return document.readyState === "loading"; - }, - args: [url, injectionId, [...scripts]], - target: {tabId, frameIds: [frameId]}, - injectImmediately: true, - } : - // mv2 browser.tabs.executeScript() - { - code: `(() => { - let injectionId = ${JSON.stringify(injectionId)}; - if (document.readyState === "complete" || - window[injectionId] || - document.URL !== ${JSON.stringify(url)} - ) return window[injectionId]; - window[injectionId] = true; - ${[...scripts].join("\n")} - 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) { - const id = getId(request); - const args = pending.get(id); - if (!args) return; - let {url, tabId} = request; - let attempts = 0; - let success = false; - const execute = mv3Callbacks ? - async () => { - const ret = await browser.scripting.executeScript(args); - return ret[0].result; - } - : async() => { - const ret = await browser.tabs.executeScript(tabId, args); - return ret[0]; - }; - 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...`); - } - if (execute()) { - success = true; - break; - } - } catch (e) { - if (/No tab\b/.test(e.message)) { - break; - } - if (!/\baccess\b/.test(e.message)) { - console.error(e.message); - } - if (!browser.tabs.executeScript) { - console.error(`MV3 fatality, cannot script tab ${tabId}! ${JSON.stringify(args)}`); - break; - } - 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 { - mv3Callbacks, - 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 |