mirror of
https://github.com/Feodor2/Mypal68.git
synced 2025-06-18 14:55:44 -04:00
483 lines
14 KiB
JavaScript
483 lines
14 KiB
JavaScript
"use strict";
|
|
|
|
let commonEvents = {
|
|
onBeforeRequest: [{ urls: ["<all_urls>"] }, ["blocking"]],
|
|
onBeforeSendHeaders: [
|
|
{ urls: ["<all_urls>"] },
|
|
["blocking", "requestHeaders"],
|
|
],
|
|
onSendHeaders: [{ urls: ["<all_urls>"] }, ["requestHeaders"]],
|
|
onBeforeRedirect: [{ urls: ["<all_urls>"] }],
|
|
onHeadersReceived: [
|
|
{ urls: ["<all_urls>"] },
|
|
["blocking", "responseHeaders"],
|
|
],
|
|
// Auth tests will need to set their own events object
|
|
// "onAuthRequired": [{urls: ["<all_urls>"]}, ["blocking", "responseHeaders"]],
|
|
onResponseStarted: [{ urls: ["<all_urls>"] }],
|
|
onCompleted: [{ urls: ["<all_urls>"] }, ["responseHeaders"]],
|
|
onErrorOccurred: [{ urls: ["<all_urls>"] }],
|
|
};
|
|
|
|
function background(events) {
|
|
const IP_PATTERN = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
|
|
|
|
let expect;
|
|
let ignore;
|
|
let defaultOrigin;
|
|
let watchAuth = Object.keys(events).includes("onAuthRequired");
|
|
let expectedIp = null;
|
|
|
|
browser.test.onMessage.addListener((msg, expected) => {
|
|
if (msg !== "set-expected") {
|
|
return;
|
|
}
|
|
expect = expected.expect;
|
|
defaultOrigin = expected.origin;
|
|
ignore = expected.ignore;
|
|
let promises = [];
|
|
// Initialize some stuff we'll need in the tests.
|
|
for (let entry of Object.values(expect)) {
|
|
// a place for the test infrastructure to store some state.
|
|
entry.test = {};
|
|
// Each entry in expected gets a Promise that will be resolved in the
|
|
// last event for that entry. This will either be onCompleted, or the
|
|
// last entry if an events list was provided.
|
|
promises.push(
|
|
new Promise(resolve => {
|
|
entry.test.resolve = resolve;
|
|
})
|
|
);
|
|
// If events was left undefined, we're expecting all normal events we're
|
|
// listening for, exclude onBeforeRedirect and onErrorOccurred
|
|
if (entry.events === undefined) {
|
|
entry.events = Object.keys(events).filter(
|
|
name => name != "onErrorOccurred" && name != "onBeforeRedirect"
|
|
);
|
|
}
|
|
if (entry.optional_events === undefined) {
|
|
entry.optional_events = [];
|
|
}
|
|
}
|
|
// When every expected entry has finished our test is done.
|
|
Promise.all(promises).then(() => {
|
|
browser.test.sendMessage("done");
|
|
});
|
|
browser.test.sendMessage("continue");
|
|
});
|
|
|
|
// Retrieve the per-file/test expected values.
|
|
function getExpected(details) {
|
|
let url = new URL(details.url);
|
|
let filename = url.pathname.split("/").pop();
|
|
if (ignore && ignore.includes(filename)) {
|
|
return;
|
|
}
|
|
let expected = expect[filename];
|
|
if (!expected) {
|
|
browser.test.fail(`unexpected request ${filename}`);
|
|
return;
|
|
}
|
|
// Save filename for redirect verification.
|
|
expected.test.filename = filename;
|
|
return expected;
|
|
}
|
|
|
|
// Process any test header modifications that can happen in request or response phases.
|
|
// If a test includes headers, it needs a complete header object, no undefined
|
|
// objects even if empty:
|
|
// request: {
|
|
// add: {"HeaderName": "value",},
|
|
// modify: {"HeaderName": "value",},
|
|
// remove: ["HeaderName",],
|
|
// },
|
|
// response: {
|
|
// add: {"HeaderName": "value",},
|
|
// modify: {"HeaderName": "value",},
|
|
// remove: ["HeaderName",],
|
|
// },
|
|
function processHeaders(phase, expected, details) {
|
|
// This should only happen once per phase [request|response].
|
|
browser.test.assertFalse(
|
|
!!expected.test[phase],
|
|
`First processing of headers for ${phase}`
|
|
);
|
|
expected.test[phase] = true;
|
|
|
|
let headers = details[`${phase}Headers`];
|
|
browser.test.assertTrue(
|
|
Array.isArray(headers),
|
|
`${phase}Headers array present`
|
|
);
|
|
|
|
let { add, modify, remove } = expected.headers[phase];
|
|
|
|
for (let name in add) {
|
|
browser.test.assertTrue(
|
|
!headers.find(h => h.name === name),
|
|
`header ${name} to be added not present yet in ${phase}Headers`
|
|
);
|
|
let header = { name: name };
|
|
if (name.endsWith("-binary")) {
|
|
header.binaryValue = Array.from(add[name], c => c.charCodeAt(0));
|
|
} else {
|
|
header.value = add[name];
|
|
}
|
|
headers.push(header);
|
|
}
|
|
|
|
let modifiedAny = false;
|
|
for (let header of headers) {
|
|
if (header.name.toLowerCase() in modify) {
|
|
header.value = modify[header.name.toLowerCase()];
|
|
modifiedAny = true;
|
|
}
|
|
}
|
|
browser.test.assertTrue(
|
|
modifiedAny,
|
|
`at least one ${phase}Headers element to modify`
|
|
);
|
|
|
|
let deletedAny = false;
|
|
for (let j = headers.length; j-- > 0; ) {
|
|
if (remove.includes(headers[j].name.toLowerCase())) {
|
|
headers.splice(j, 1);
|
|
deletedAny = true;
|
|
}
|
|
}
|
|
browser.test.assertTrue(
|
|
deletedAny,
|
|
`at least one ${phase}Headers element to delete`
|
|
);
|
|
|
|
return headers;
|
|
}
|
|
|
|
// phase is request or response.
|
|
function checkHeaders(phase, expected, details) {
|
|
if (!/^https?:/.test(details.url)) {
|
|
return;
|
|
}
|
|
|
|
let headers = details[`${phase}Headers`];
|
|
browser.test.assertTrue(
|
|
Array.isArray(headers),
|
|
`valid ${phase}Headers array`
|
|
);
|
|
|
|
let { add, modify, remove } = expected.headers[phase];
|
|
for (let name in add) {
|
|
let value = headers.find(h => h.name.toLowerCase() === name.toLowerCase())
|
|
.value;
|
|
browser.test.assertEq(
|
|
value,
|
|
add[name],
|
|
`header ${name} correctly injected in ${phase}Headers`
|
|
);
|
|
}
|
|
|
|
for (let name in modify) {
|
|
let value = headers.find(h => h.name.toLowerCase() === name.toLowerCase())
|
|
.value;
|
|
browser.test.assertEq(
|
|
value,
|
|
modify[name],
|
|
`header ${name} matches modified value`
|
|
);
|
|
}
|
|
|
|
for (let name of remove) {
|
|
let found = headers.find(
|
|
h => h.name.toLowerCase() === name.toLowerCase()
|
|
);
|
|
browser.test.assertFalse(
|
|
!!found,
|
|
`deleted header ${name} still found in ${phase}Headers`
|
|
);
|
|
}
|
|
}
|
|
|
|
let listeners = {
|
|
onBeforeRequest(expected, details, result) {
|
|
// Save some values to test request consistency in later events.
|
|
browser.test.assertTrue(
|
|
details.tabId !== undefined,
|
|
`tabId ${details.tabId}`
|
|
);
|
|
browser.test.assertTrue(
|
|
details.requestId !== undefined,
|
|
`requestId ${details.requestId}`
|
|
);
|
|
// Validate requestId if it's already set, this happens with redirects.
|
|
if (expected.test.requestId !== undefined) {
|
|
browser.test.assertEq(
|
|
"string",
|
|
typeof expected.test.requestId,
|
|
`requestid ${expected.test.requestId} is string`
|
|
);
|
|
browser.test.assertEq(
|
|
"string",
|
|
typeof details.requestId,
|
|
`requestid ${details.requestId} is string`
|
|
);
|
|
browser.test.assertEq(
|
|
"number",
|
|
typeof parseInt(details.requestId, 10),
|
|
"parsed requestid is number"
|
|
);
|
|
browser.test.assertEq(
|
|
expected.test.requestId,
|
|
details.requestId,
|
|
"redirects will keep the same requestId"
|
|
);
|
|
} else {
|
|
// Save any values we want to validate in later events.
|
|
expected.test.requestId = details.requestId;
|
|
expected.test.tabId = details.tabId;
|
|
}
|
|
// Tests we don't need to do every event.
|
|
browser.test.assertTrue(
|
|
details.type.toUpperCase() in browser.webRequest.ResourceType,
|
|
`valid resource type ${details.type}`
|
|
);
|
|
if (details.type == "main_frame") {
|
|
browser.test.assertEq(
|
|
0,
|
|
details.frameId,
|
|
"frameId is zero when type is main_frame, see bug 1329299"
|
|
);
|
|
}
|
|
},
|
|
onBeforeSendHeaders(expected, details, result) {
|
|
if (expected.headers && expected.headers.request) {
|
|
result.requestHeaders = processHeaders("request", expected, details);
|
|
}
|
|
if (expected.redirect) {
|
|
browser.test.log(`${name} redirect request`);
|
|
result.redirectUrl = details.url.replace(
|
|
expected.test.filename,
|
|
expected.redirect
|
|
);
|
|
}
|
|
},
|
|
onBeforeRedirect() {},
|
|
onSendHeaders(expected, details, result) {
|
|
if (expected.headers && expected.headers.request) {
|
|
checkHeaders("request", expected, details);
|
|
}
|
|
},
|
|
onResponseStarted() {},
|
|
onHeadersReceived(expected, details, result) {
|
|
let expectedStatus = expected.status || 200;
|
|
// If authentication is being requested we don't fail on the status code.
|
|
if (watchAuth && [401, 407].includes(details.statusCode)) {
|
|
expectedStatus = details.statusCode;
|
|
}
|
|
browser.test.assertEq(
|
|
expectedStatus,
|
|
details.statusCode,
|
|
`expected HTTP status received for ${details.url} ${details.statusLine}`
|
|
);
|
|
if (expected.headers && expected.headers.response) {
|
|
result.responseHeaders = processHeaders("response", expected, details);
|
|
}
|
|
},
|
|
onAuthRequired(expected, details, result) {
|
|
result.authCredentials = expected.authInfo;
|
|
},
|
|
onCompleted(expected, details, result) {
|
|
// If we have already completed a GET request for this url,
|
|
// and it was found, we expect for the response to come fromCache.
|
|
// expected.cached may be undefined, force boolean.
|
|
if (typeof expected.cached === "boolean") {
|
|
let expectCached =
|
|
expected.cached &&
|
|
details.method === "GET" &&
|
|
details.statusCode != 404;
|
|
browser.test.assertEq(
|
|
expectCached,
|
|
details.fromCache,
|
|
"fromCache is correct"
|
|
);
|
|
}
|
|
// We can only tell IPs for non-cached HTTP requests.
|
|
if (!details.fromCache && /^https?:/.test(details.url)) {
|
|
browser.test.assertTrue(
|
|
IP_PATTERN.test(details.ip),
|
|
`IP for ${details.url} looks IP-ish: ${details.ip}`
|
|
);
|
|
|
|
// We can't easily predict the IP ahead of time, so just make
|
|
// sure they're all consistent.
|
|
expectedIp = expectedIp || details.ip;
|
|
browser.test.assertEq(
|
|
expectedIp,
|
|
details.ip,
|
|
`correct ip for ${details.url}`
|
|
);
|
|
}
|
|
if (expected.headers && expected.headers.response) {
|
|
checkHeaders("response", expected, details);
|
|
}
|
|
},
|
|
onErrorOccurred(expected, details, result) {
|
|
if (expected.error) {
|
|
if (Array.isArray(expected.error)) {
|
|
browser.test.assertTrue(
|
|
expected.error.includes(details.error),
|
|
"expected error message received in onErrorOccurred"
|
|
);
|
|
} else {
|
|
browser.test.assertEq(
|
|
expected.error,
|
|
details.error,
|
|
"expected error message received in onErrorOccurred"
|
|
);
|
|
}
|
|
}
|
|
},
|
|
};
|
|
|
|
function getListener(name) {
|
|
return details => {
|
|
let result = {};
|
|
browser.test.log(`${name} ${details.requestId} ${details.url}`);
|
|
let expected = getExpected(details);
|
|
if (!expected) {
|
|
return result;
|
|
}
|
|
let expectedEvent = expected.events[0] == name;
|
|
if (expectedEvent) {
|
|
expected.events.shift();
|
|
} else {
|
|
// e10s vs. non-e10s errors can end with either onCompleted or onErrorOccurred
|
|
expectedEvent = expected.optional_events.includes(name);
|
|
}
|
|
browser.test.assertTrue(expectedEvent, `received ${name}`);
|
|
browser.test.assertEq(
|
|
expected.type,
|
|
details.type,
|
|
"resource type is correct"
|
|
);
|
|
browser.test.assertEq(
|
|
expected.origin || defaultOrigin,
|
|
details.originUrl,
|
|
"origin is correct"
|
|
);
|
|
|
|
if (name != "onBeforeRequest") {
|
|
// On events after onBeforeRequest, check the previous values.
|
|
browser.test.assertEq(
|
|
expected.test.requestId,
|
|
details.requestId,
|
|
"correct requestId"
|
|
);
|
|
browser.test.assertEq(
|
|
expected.test.tabId,
|
|
details.tabId,
|
|
"correct tabId"
|
|
);
|
|
}
|
|
try {
|
|
listeners[name](expected, details, result);
|
|
} catch (e) {
|
|
browser.test.fail(`unexpected webrequest failure ${name} ${e}`);
|
|
}
|
|
|
|
if (expected.cancel && expected.cancel == name) {
|
|
browser.test.log(`${name} cancel request`);
|
|
browser.test.sendMessage("cancelled");
|
|
result.cancel = true;
|
|
}
|
|
// If we've used up all the events for this test, resolve the promise.
|
|
// If something wrong happens and more events come through, there will be
|
|
// failures.
|
|
if (expected.events.length <= 0) {
|
|
expected.test.resolve();
|
|
}
|
|
return result;
|
|
};
|
|
}
|
|
|
|
for (let [name, args] of Object.entries(events)) {
|
|
browser.test.log(`adding listener for ${name}`);
|
|
try {
|
|
browser.webRequest[name].addListener(getListener(name), ...args);
|
|
} catch (e) {
|
|
browser.test.assertTrue(
|
|
/\brequestBody\b/.test(e.message),
|
|
"Request body is unsupported"
|
|
);
|
|
|
|
// RequestBody is disabled in release builds.
|
|
if (!/\brequestBody\b/.test(e.message)) {
|
|
throw e;
|
|
}
|
|
|
|
args.splice(args.indexOf("requestBody"), 1);
|
|
browser.webRequest[name].addListener(getListener(name), ...args);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* exported makeExtension */
|
|
|
|
function makeExtension(events = commonEvents) {
|
|
return ExtensionTestUtils.loadExtension({
|
|
manifest: {
|
|
permissions: ["webRequest", "webRequestBlocking", "<all_urls>"],
|
|
},
|
|
background: `(${background})(${JSON.stringify(events)})`,
|
|
});
|
|
}
|
|
|
|
/* exported addStylesheet */
|
|
|
|
function addStylesheet(file) {
|
|
let link = document.createElement("link");
|
|
link.setAttribute("rel", "stylesheet");
|
|
link.setAttribute("href", file);
|
|
document.body.appendChild(link);
|
|
}
|
|
|
|
/* exported addLink */
|
|
|
|
function addLink(file) {
|
|
let a = document.createElement("a");
|
|
a.setAttribute("href", file);
|
|
a.setAttribute("target", "_blank");
|
|
a.setAttribute("rel", "opener");
|
|
document.body.appendChild(a);
|
|
return a;
|
|
}
|
|
|
|
/* exported addImage */
|
|
|
|
function addImage(file) {
|
|
let img = document.createElement("img");
|
|
img.setAttribute("src", file);
|
|
document.body.appendChild(img);
|
|
}
|
|
|
|
/* exported addScript */
|
|
|
|
function addScript(file) {
|
|
let script = document.createElement("script");
|
|
script.setAttribute("type", "text/javascript");
|
|
script.setAttribute("src", file);
|
|
document
|
|
.getElementsByTagName("head")
|
|
.item(0)
|
|
.appendChild(script);
|
|
}
|
|
|
|
/* exported addFrame */
|
|
|
|
function addFrame(file) {
|
|
let frame = document.createElement("iframe");
|
|
frame.setAttribute("width", "200");
|
|
frame.setAttribute("height", "200");
|
|
frame.setAttribute("src", file);
|
|
document.body.appendChild(frame);
|
|
}
|