From 81bd93a72d0b49fd78908f7c467ae9f324791e23 Mon Sep 17 00:00:00 2001 From: hackademix Date: Mon, 2 Jul 2018 01:50:32 +0200 Subject: Simplified and apparently more reliable+flexible+efficient dynamic script injection method. --- src/bg/RequestGuard.js | 2 +- src/bg/RequestUtil.js | 161 ++++++++++++++--------------------------------- src/content/media.js | 27 ++++---- src/content/webglHook.js | 2 +- src/lib/log.js | 2 +- 5 files changed, 65 insertions(+), 129 deletions(-) (limited to 'src') diff --git a/src/bg/RequestGuard.js b/src/bg/RequestGuard.js index 0f1558c..3304d02 100644 --- a/src/bg/RequestGuard.js +++ b/src/bg/RequestGuard.js @@ -422,7 +422,7 @@ var RequestGuard = (() => { } } - debug(`CSP blocker:`, blocker); + debug(`CSP blocker on %s:`, request.url, blocker); if (blocker) { if (header) { header.value = CSP.inject(header.value, blocker); diff --git a/src/bg/RequestUtil.js b/src/bg/RequestUtil.js index 1ebbbaa..2020286 100644 --- a/src/bg/RequestUtil.js +++ b/src/bg/RequestUtil.js @@ -1,130 +1,61 @@ 'use strict'; { - let runningScripts = new Map(); - + let pendingRequests = new Map(); + let cleanup = r => { + pendingRequests.delete(r.requestId); + }; + let filter = { + urls: [""], + types: ["main_frame", "sub_frame", "object"] + }; + browser.webRequest.onCompleted.addListener(cleanup, filter); + browser.webRequest.onErrorOccurred.addListener(cleanup, filter); var RequestUtil = { async executeOnStart(request, details) { let {requestId, tabId, frameId} = request; - details = Object.assign({ - runAt: "document_start", - frameId, - }, details); - browser.tabs.executeScript(tabId, details); - return; - let filter = browser.webRequest.filterResponseData(requestId); - filter.onstart = event => { - browser.tabs.executeScript(tabId, details); - debug("Execute on start", details); - filter.write(new Uint8Array()); - }; - filter.ondata = event => { - filter.write(event.data); - filter.disconnect(); - - } - }, - async executeOnStartCS(request, details) { - let {url, requestId, tabId, frameId} = request; - - let urlObj = new URL(url); - if (urlObj.hash || urlObj.port || urlObj.username) { - urlObj.hash = urlObj.port = urlObj.username = ""; - url = urlObj.toString(); + let scripts = pendingRequests.get(requestId); + let scriptKey = JSON.stringify(details); + if (!scripts) { + pendingRequests.set(requestId, scripts = new Map()); + scripts.set(scriptKey, details); + } else { + scripts.set(scriptKey, details); + return; } - let wr = browser.webRequest; - let filter = { - urls: [`${urlObj.origin}/*`], - types: ["main_frame", "sub_frame", "object"] - }; - let finalize; - let cleanup = r => { - if (cleanup && r.requestId === requestId) { - wr.onCompleted.removeListener(cleanup); - wr.onErrorOccurred.removeListener(cleanup); - cleanup = null; - if (finalize) { - finalize(); - } - } - }; - wr.onCompleted.addListener(cleanup, filter); - wr.onErrorOccurred.addListener(cleanup, filter); - - details = Object.assign({ - runAt: "document_start", - frameId, - }, details); - - if (browser.contentScripts) { - let js = [{}]; - if (details.file) js[0].file = details.file; - else if (details.code) js[0].code = details.code; - let settings = { - "runAt": details.runAt, - js, - matches: [url], - allFrames: frameId !== 0, - } - // let's try to avoid duplicates - let key = JSON.stringify(settings); - if (runningScripts.has(key)) { - let scriptRef = runningScripts.get(key); - scriptRef.count++; - return; - } - if (settings.allFrames) { - // let's check whether the same script is registered for top frames: - // if it is, let's unregister it first to avoid duplicates - settings.allFrames = false; - let topKey = JSON.stringify(settings); - settings.allFrames = true; - if (runningScripts.has(topKey)) { - let topScript = runningScripts.get(topKey); - try { - topScript.unregister(); - } catch (e) { - error(e); - } finally { - runningScripts.delete(topKey); - } - } - } - let script = await browser.contentScripts.register(settings); - debug("Content script %o registered.", settings); - finalize = () => { - debug("Finalizing content script %o...", settings); + let filter = browser.webRequest.filterResponseData(requestId); + let buffer = []; + filter.onstart = async event => { + filter.write(new Uint8Array()); + for (let details of scripts.values()) { + details = Object.assign({ + runAt: "document_start", + frameId, + }, details); try { - script.unregister(); - runningScripts.delete(key); - debug("Content script %o unregistered!", settings); - } finally { - finalize = null; + await browser.tabs.executeScript(tabId, details); + debug("Execute on start OK", request.url, details); + } catch (e) { + error(e, "Execute on start failed", request.url, details); } } - runningScripts.set(key, script); - if (!cleanup) { // the request has already been interrupted - finalize(); + if (buffer.length) { + debug("Flushing %s buffer chunks", buffer.length); + for (let chunk of buffer) { + filter.write(chunk); + } + filter.disconnect(); + buffer = null; } - - return; - } - - function listener(r) { - if (r.requestId === requestId) { - browser.tabs.executeScript(tabId, details); - finalize(); - finalize = null; + }; + filter.ondata = event => { + if (buffer) { + buffer.push(event.data); + return; } + filter.write(event.data); + filter.disconnect(); } - finalize = () => { - wr.onResponseStarted.removeListener(listener); - } - wr.onResponseStarted.addListener(listener, filter); - debug("Executing %o", details); - - }, - - + } } } diff --git a/src/content/media.js b/src/content/media.js index 22bf014..4ca8942 100644 --- a/src/content/media.js +++ b/src/content/media.js @@ -1,4 +1,4 @@ -console.log("Media Hook", document.documentElement.innerHTML); +debug("Media Hook (blocked %s)", !!window.mediaBlocker, document.URL, document.documentElement && document.documentElement.innerHTML); try { (() => { let unpatched = new Map(); @@ -25,9 +25,6 @@ try { patch(window.MediaSource.prototype, "addSourceBuffer", function(mime, ...args) { let ms = this; let urls = urlMap.get(ms); - let me = Array.from(document.querySelectorAll("video,audio")) - .find(e => e.srcObject === ms || urls && urls.has(e.src)); - let exposedMime = `${mime} (MSE)`; let request = { id: "noscript-media", @@ -40,13 +37,21 @@ try { notifyPage(); if (window.mediaBlocker) { - try { - let ph = PlaceHolder.create("media", request); - ph.replace(me); - PlaceHolder.listen(); - } catch (e) { - error(e); - } + (async () => { + let me = Array.from(document.querySelectorAll("video,audio")) + .find(e => e.srcObject === ms || urls && urls.has(e.src)); + + if (!me) return; + let exposedMime = `${mime} (MSE)`; + + try { + let ph = PlaceHolder.create("media", request); + ph.replace(me); + PlaceHolder.listen(); + } catch (e) { + error(e); + } + })(); throw new Error(`${exposedMime} blocked by NoScript`); } diff --git a/src/content/webglHook.js b/src/content/webglHook.js index ba0d769..5b83025 100644 --- a/src/content/webglHook.js +++ b/src/content/webglHook.js @@ -1,4 +1,4 @@ -console.log("WebGL Hook", document.documentElement.innerHTML); +console.log("WebGL Hook", document.URL, document.documentElement && document.documentElement.innerHTML); try { let proto = HTMLCanvasElement.prototype; let getContext = proto.getContext; diff --git a/src/lib/log.js b/src/lib/log.js index fe38b87..0f12698 100644 --- a/src/lib/log.js +++ b/src/lib/log.js @@ -9,6 +9,6 @@ console.debug(`${PREFIX} ${msg}`, ...rest); } function error(e, msg, ...rest) { - console.error(`${PREFIX} ${msg}`, e, e.message, e.stack); + console.error(`${PREFIX} ${msg}`, ...rest, e, e.message, e.stack); } } -- cgit v1.2.3