181 lines
5.4 KiB
JavaScript
181 lines
5.4 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
"use strict";
|
|
|
|
/* globals browser */
|
|
|
|
const replaceStringInRequest = (
|
|
requestId,
|
|
inString,
|
|
outString,
|
|
inEncoding = "utf-8"
|
|
) => {
|
|
const filter = browser.webRequest.filterResponseData(requestId);
|
|
const decoder = new TextDecoder(inEncoding);
|
|
const encoder = new TextEncoder();
|
|
const RE = new RegExp(inString, "g");
|
|
const carryoverLength = inString.length;
|
|
let carryover = "";
|
|
|
|
filter.ondata = event => {
|
|
const replaced = (
|
|
carryover + decoder.decode(event.data, { stream: true })
|
|
).replace(RE, outString);
|
|
filter.write(encoder.encode(replaced.slice(0, -carryoverLength)));
|
|
carryover = replaced.slice(-carryoverLength);
|
|
};
|
|
|
|
filter.onstop = () => {
|
|
if (carryover.length) {
|
|
filter.write(encoder.encode(carryover));
|
|
}
|
|
filter.close();
|
|
};
|
|
};
|
|
|
|
const interventionListeners = new Map();
|
|
|
|
function rememberListener(intervention, key, listener) {
|
|
if (!interventionListeners.has(intervention)) {
|
|
interventionListeners.set(intervention, new Map());
|
|
}
|
|
const map = interventionListeners.get(intervention);
|
|
if (map.has(key)) {
|
|
throw new Error(`multiple custom listeners have the same key ${key}`);
|
|
}
|
|
map.set(key, listener);
|
|
}
|
|
|
|
function forgetListener(intervention, key) {
|
|
const map = interventionListeners.get(intervention);
|
|
if (!map) {
|
|
return undefined;
|
|
}
|
|
const listener = map.get(key);
|
|
map.delete(key);
|
|
return listener;
|
|
}
|
|
|
|
var CUSTOM_FUNCTIONS = {
|
|
alter_response_headers: {
|
|
details: ["headers", "replacement"],
|
|
optionalDetails: ["fallback", "replace", "types", "urls"],
|
|
getKey(config) {
|
|
return `alter_headers:${JSON.stringify(config)}`;
|
|
},
|
|
enable(config, intervention) {
|
|
let { fallback, headers, replace, replacement, types, urls } = config;
|
|
if (!urls) {
|
|
urls = Object.values(intervention.bugs)
|
|
.map(bug => bug.matches)
|
|
.flat()
|
|
.filter(v => v !== undefined);
|
|
}
|
|
const regex =
|
|
replace === null ? null : new RegExp(replace ?? "^.*$", "gi");
|
|
const listener = evt => {
|
|
let found = false;
|
|
const responseHeaders = [];
|
|
for (const header of evt.responseHeaders) {
|
|
if (headers.includes(header.name.toLowerCase())) {
|
|
if (regex !== null && replacement !== null) {
|
|
found = true;
|
|
const value = header.value.replaceAll(regex, replacement);
|
|
responseHeaders.push({ name: header.name, value });
|
|
}
|
|
} else {
|
|
responseHeaders.push(header);
|
|
}
|
|
}
|
|
if (!found && (replace === undefined || typeof fallback === "string")) {
|
|
const value = fallback ?? replacement;
|
|
if (value !== null) {
|
|
responseHeaders.push({
|
|
name: headers[0],
|
|
value,
|
|
});
|
|
}
|
|
}
|
|
return { responseHeaders };
|
|
};
|
|
browser.webRequest.onHeadersReceived.addListener(
|
|
listener,
|
|
{ types, urls },
|
|
["blocking", "responseHeaders"]
|
|
);
|
|
rememberListener(intervention, this.getKey(config), listener);
|
|
},
|
|
disable(config, intervention) {
|
|
const listener = forgetListener(intervention, this.getKey(config));
|
|
if (listener) {
|
|
browser.webRequest.onHeadersReceived.removeListener(listener);
|
|
}
|
|
},
|
|
},
|
|
replace_string_in_request: {
|
|
details: ["find", "replace", "urls"],
|
|
optionalDetails: ["types"],
|
|
enable(details) {
|
|
const { find, replace, urls, types } = details;
|
|
const listener = (details.listener = ({ requestId }) => {
|
|
replaceStringInRequest(requestId, find, replace);
|
|
return {};
|
|
});
|
|
browser.webRequest.onBeforeRequest.addListener(
|
|
listener,
|
|
{ urls, types },
|
|
["blocking"]
|
|
);
|
|
},
|
|
disable(details) {
|
|
const { listener } = details;
|
|
browser.webRequest.onBeforeRequest.removeListener(listener);
|
|
delete details.listener;
|
|
},
|
|
},
|
|
run_script_before_request: {
|
|
details: ["message", "urls", "script"],
|
|
optionalDetails: ["types"],
|
|
enable(details, intervention) {
|
|
const { bug } = intervention;
|
|
const { message, script, types, urls } = details;
|
|
const warning = `${message} See https://bugzilla.mozilla.org/show_bug.cgi?id=${bug} for details.`;
|
|
|
|
const listener = (details.listener = evt => {
|
|
const { tabId, frameId } = evt;
|
|
return browser.tabs
|
|
.executeScript(tabId, {
|
|
file: script,
|
|
frameId,
|
|
runAt: "document_start",
|
|
})
|
|
.then(() => {
|
|
browser.tabs.executeScript(tabId, {
|
|
code: `console.warn(${JSON.stringify(warning)})`,
|
|
runAt: "document_start",
|
|
});
|
|
})
|
|
.catch(err => {
|
|
console.error(
|
|
"Error running script before request for webcompat intervention for bug",
|
|
bug,
|
|
err
|
|
);
|
|
});
|
|
});
|
|
|
|
browser.webRequest.onBeforeRequest.addListener(
|
|
listener,
|
|
{ urls, types: types || ["script"] },
|
|
["blocking"]
|
|
);
|
|
},
|
|
disable(details) {
|
|
const { listener } = details;
|
|
browser.webRequest.onBeforeRequest.removeListener(listener);
|
|
delete details.listener;
|
|
},
|
|
},
|
|
};
|