Simplified and apparently more reliable+flexible+efficient dynamic script injection method.

This commit is contained in:
hackademix 2018-07-02 01:50:32 +02:00
parent 673c881559
commit 81bd93a72d
5 changed files with 68 additions and 132 deletions

View File

@ -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);

View File

@ -1,130 +1,61 @@
'use strict';
{
let runningScripts = new Map();
let pendingRequests = new Map();
let cleanup = r => {
pendingRequests.delete(r.requestId);
};
let filter = {
urls: ["<all_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;
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 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);
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());
try {
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);
}
}
if (buffer.length) {
debug("Flushing %s buffer chunks", buffer.length);
for (let chunk of buffer) {
filter.write(chunk);
}
filter.disconnect();
buffer = null;
}
};
filter.ondata = event => {
if (buffer) {
buffer.push(event.data);
return;
}
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 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);
try {
script.unregister();
runningScripts.delete(key);
debug("Content script %o unregistered!", settings);
} finally {
finalize = null;
}
}
runningScripts.set(key, script);
if (!cleanup) { // the request has already been interrupted
finalize();
}
return;
}
function listener(r) {
if (r.requestId === requestId) {
browser.tabs.executeScript(tabId, details);
finalize();
finalize = null;
}
}
finalize = () => {
wr.onResponseStarted.removeListener(listener);
}
wr.onResponseStarted.addListener(listener, filter);
debug("Executing %o", details);
},
}
}

View File

@ -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,6 +37,13 @@ try {
notifyPage();
if (window.mediaBlocker) {
(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);
@ -47,6 +51,7 @@ try {
} catch (e) {
error(e);
}
})();
throw new Error(`${exposedMime} blocked by NoScript`);
}

View File

@ -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;

View File

@ -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);
}
}