Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* Any copyright is dedicated to the Public Domain.
"use strict";
declTest("destroy actor by iframe remove", {
allFrames: true,
async test(browser) {
await SpecialPowers.spawn(browser, [], async function () {
// Create and append an iframe into the window's document.
let frame = content.document.createElement("iframe");
frame.id = "frame";
content.document.body.appendChild(frame);
await ContentTaskUtils.waitForEvent(frame, "load");
is(content.window.frames.length, 1, "There should be an iframe.");
let child = frame.contentWindow.windowGlobalChild;
let actorChild = child.getActor("TestWindow");
ok(actorChild, "JSWindowActorChild should have value.");
{
let error = actorChild.uninitializedGetterError;
const prop = "contentWindow";
Assert.ok(
error,
`Should get error accessing '${prop}' before actor initialization`
);
if (error) {
Assert.equal(
error.name,
"InvalidStateError",
"Error should be an InvalidStateError"
);
Assert.equal(
error.message,
`JSWindowActorChild.${prop} getter: Cannot access property '${prop}' before actor is initialized`,
"Error should have informative message"
);
}
}
let didDestroyPromise = new Promise(resolve => {
const TOPIC = "test-js-window-actor-diddestroy";
Services.obs.addObserver(function obs(subject, topic, data) {
ok(data, "didDestroyCallback data should be true.");
is(subject, actorChild, "Should have this value");
Services.obs.removeObserver(obs, TOPIC);
// Make a trip through the event loop to ensure that the
// actor's manager has been cleared before running remaining
// checks.
Services.tm.dispatchToMainThread(resolve);
}, TOPIC);
});
info("Remove frame");
content.document.getElementById("frame").remove();
await didDestroyPromise;
Assert.throws(
() => child.getActor("TestWindow"),
/InvalidStateError/,
"Should throw if frame destroy."
);
for (let prop of [
"document",
"browsingContext",
"docShell",
"contentWindow",
]) {
let error;
try {
void actorChild[prop];
} catch (e) {
error = e;
}
Assert.ok(
error,
`Should get error accessing '${prop}' after actor destruction`
);
if (error) {
Assert.equal(
error.name,
"InvalidStateError",
"Error should be an InvalidStateError"
);
Assert.equal(
error.message,
`JSWindowActorChild.${prop} getter: Cannot access property '${prop}' after actor 'TestWindow' has been destroyed`,
"Error should have informative message"
);
}
}
});
},
});
declTest("destroy actor by page navigates", {
allFrames: true,
async test(browser) {
info("creating an in-process frame");
await SpecialPowers.spawn(browser, [URL], async function (url) {
let frame = content.document.createElement("iframe");
frame.src = url;
content.document.body.appendChild(frame);
});
info("navigating page");
await SpecialPowers.spawn(browser, [TEST_URL], async function (url) {
let frame = content.document.querySelector("iframe");
frame.contentWindow.location = url;
let child = frame.contentWindow.windowGlobalChild;
let actorChild = child.getActor("TestWindow");
ok(actorChild, "JSWindowActorChild should have value.");
let didDestroyPromise = new Promise(resolve => {
const TOPIC = "test-js-window-actor-diddestroy";
Services.obs.addObserver(function obs(subject, topic, data) {
ok(data, "didDestroyCallback data should be true.");
is(subject, actorChild, "Should have this value");
Services.obs.removeObserver(obs, TOPIC);
resolve();
}, TOPIC);
});
await Promise.all([
didDestroyPromise,
ContentTaskUtils.waitForEvent(frame, "load"),
]);
Assert.throws(
() => child.getActor("TestWindow"),
/InvalidStateError/,
"Should throw if frame destroy."
);
});
},
});
declTest("destroy actor by tab being closed", {
allFrames: true,
async test() {
info("creating a new tab");
let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL);
let newTabBrowser = newTab.linkedBrowser;
let parent =
newTabBrowser.browsingContext.currentWindowGlobal.getActor("TestWindow");
ok(parent, "JSWindowActorParent should have value.");
// We can't depend on `SpecialPowers.spawn` to resolve our promise, as the
// frame message manager will be being shut down at the same time. Instead
// send messages over the per-process message manager which should still be
// active.
let didDestroyPromise = new Promise(resolve => {
Services.ppmm.addMessageListener(
"test-jswindowactor-diddestroy",
function onmessage() {
Services.ppmm.removeMessageListener(
"test-jswindowactor-diddestroy",
onmessage
);
resolve();
}
);
});
info("setting up destroy listeners");
await SpecialPowers.spawn(newTabBrowser, [], () => {
let child = content.windowGlobalChild;
let actorChild = child.getActor("TestWindow");
ok(actorChild, "JSWindowActorChild should have value.");
Services.obs.addObserver(function obs(subject, topic, data) {
if (subject != actorChild) {
return;
}
dump("DidDestroy called\n");
Services.obs.removeObserver(obs, "test-js-window-actor-diddestroy");
Services.cpmm.sendAsyncMessage("test-jswindowactor-diddestroy", data);
}, "test-js-window-actor-diddestroy");
});
info("removing new tab");
await BrowserTestUtils.removeTab(newTab);
info("waiting for destroy callbacks to fire");
await didDestroyPromise;
info("got didDestroy callback");
},
});