async
function newFocusedWindow(trigger, isInitialBlank =
false) {
let winPromise = BrowserTestUtils.domWindowOpenedAndLoaded();
let delayedStartupPromise = BrowserTestUtils.waitForNewWindow();
await trigger();
let win = await winPromise;
// New windows get focused after the first paint, see bug 1262946,
// but this is racy for the initial about:blank
if (!isInitialBlank) {
await BrowserTestUtils.waitForContentEvent(
win.gBrowser.selectedBrowser,
"MozAfterPaint"
);
}
else {
await
new Promise(res =>
win.requestAnimationFrame(() => win.requestAnimationFrame(res))
);
}
await delayedStartupPromise;
return win;
}
const JS_CACHE_BASE_URL =
"http://mochi.test:8888/browser/dom/base/test/";
function ev(event, file, hasElement = !!file) {
return {
event,
url: file ? JS_CACHE_BASE_URL + file : undefined,
hasElement,
};
}
function unordered(list) {
return {
unordered: list,
};
}
function optional_ev(...args) {
const event = ev(...args);
event.optional =
true;
return event;
}
async
function jsCacheContentTask(test, item) {
const defaultSkippedEvents = test.skippedEvents ?? [
// The compilation is not target of this test.
"compile:main thread",
// This is triggered by 'invalidateMemory' below, but we don't have to
// track it.
"memorycache:invalidate",
// The disk cache handling for the in-memory cache can happen multiple
// times depending on the scheduling and speed
// (e.g. debug vs opt, verify mode).
"diskcache:noschedule",
];
const nonSkippedEvents = test.nonSkippedEvents ?? [];
const skippedEvents = defaultSkippedEvents.filter(
ev => !nonSkippedEvents.includes(ev)
);
function match(param, event) {
if (event.event !== param.event) {
return
false;
}
if (param.url && event.url !== param.url) {
return
false;
}
if (event.hasElement) {
if (param.id !==
"watchme") {
return
false;
}
}
else {
if (param.id) {
return
false;
}
}
return
true;
}
function consumeIfMatched(param, events) {
while (
"optional" in events[
0]) {
if (match(param, events[
0])) {
events.shift();
return
true;
}
dump(
"@@@ Skip optional event: " + events[
0].event +
"\n");
events.shift();
}
if (
"unordered" in events[
0]) {
const unordered = events[
0].unordered;
for (let i =
0; i < unordered.length; i++) {
if (match(param, unordered[i])) {
unordered.splice(i,
1);
if (!unordered.length) {
events.shift();
}
return
true;
}
}
return
false;
}
if (match(param, events[
0])) {
events.shift();
return
true;
}
return
false;
}
let result =
true;
const { promise, resolve, reject } = Promise.withResolvers();
const observer =
function (subject, topic, data) {
const param = {};
for (
const line of data.split(
"\n")) {
const m = line.match(/^([^:]+):(.*)/);
param[m[
1]] = m[
2];
}
if (consumeIfMatched(param, item.events)) {
dump(
"@@@ Got expected event: " + data +
"\n");
if (item.events.length ===
0) {
resolve();
}
}
else if (skippedEvents.includes(param.event)) {
dump(
"@@@ Ignoring: " + data +
"\n");
}
else {
dump(
"@@@ Got unexpected event: " + data +
"\n");
dump(
"@@@ Expected: " + JSON.stringify(item.events[
0]) +
"\n");
result =
false;
}
};
Services.obs.addObserver(observer,
"ScriptLoaderTest");
const script = content.document.createElement(
"script");
script.id =
"watchme";
if (test.module || item.module) {
script.type =
"module";
}
if (item.sri) {
script.integrity = item.sri;
}
if (item.nonce) {
script.nonce = item.nonce;
}
script.src = item.file;
content.document.body.appendChild(script);
await promise;
Services.obs.removeObserver(observer,
"ScriptLoaderTest");
return result;
}
async
function runJSCacheTests(tests) {
await BrowserTestUtils.withNewTab(
JS_CACHE_BASE_URL +
"empty.html",
async browser => {
const tab = gBrowser.getTabForBrowser(browser);
for (
const test of tests) {
ChromeUtils.clearResourceCache();
Services.cache2.clear();
if (test.useServiceWorker) {
await SpecialPowers.spawn(browser, [], async () => {
const registration = await content.navigator.serviceWorker.register(
"file_js_cache_sw.js",
{ scope:
"./" }
);
const sw = registration.installing || registration.active;
await
new Promise(resolve => {
function onStateChange() {
if (sw.state ===
"activated") {
sw.removeEventListener(
"statechange", onStateChange);
resolve();
}
}
sw.addEventListener(
"statechange", onStateChange);
onStateChange();
});
});
}
for (let i =
0; i < test.items.length; i++) {
const item = test.items[i];
info(`start: ${test.title} (item ${i})`);
if (!test.skipReload) {
// Make sure the test starts in clean document.
await BrowserTestUtils.reloadTab(tab);
}
if (item.clearMemory) {
info(
"clear memory cache");
ChromeUtils.clearResourceCache();
}
if (item.invalidateMemory) {
info(
"invalidate memory cache");
ChromeUtils.invalidateResourceCache();
}
if (item.clearDisk) {
info(
"clear disk cache");
Services.cache2.clear();
}
if (item.memoryPressureLowMemory) {
info(
"notifying memory pressure low-memory");
await SpecialPowers.spawn(browser, [], () => {
Services.obs.notifyObservers(
null,
"memory-pressure",
"low-memory"
);
});
}
if (item.memoryPressureHeapMinimize) {
info(
"notifying memory pressure heap-minimize");
await SpecialPowers.spawn(browser, [], () => {
Services.obs.notifyObservers(
null,
"memory-pressure",
"heap-minimize"
);
});
}
if (item.memoryPressureStop) {
info(
"notifying memory pressure stop");
await SpecialPowers.spawn(browser, [], () => {
Services.obs.notifyObservers(
null,
"memory-pressure-stop");
});
}
const result = await SpecialPowers.spawn(
browser,
[test, item],
jsCacheContentTask
);
ok(result,
"Received expected events");
}
if (test.useServiceWorker) {
await SpecialPowers.spawn(browser, [], async () => {
const registration =
await content.navigator.serviceWorker.getRegistration();
registration.unregister();
});
}
ok(
true,
"end: " + test.title);
}
}
);
ok(
true,
"Finished all tests");
}