1
0
Fork 0
firefox/browser/extensions/webcompat/lib/custom_functions.js
Daniel Baumann 5e9a113729
Adding upstream version 140.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-25 09:37:52 +02:00

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;
},
},
};