464 lines
14 KiB
JavaScript
464 lines
14 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
"use strict";
|
|
|
|
// Test the TargetCommand API when bfcache navigations happen
|
|
|
|
const TEST_COM_URL = URL_ROOT_SSL + "simple_document.html";
|
|
|
|
add_task(async function () {
|
|
// Disable the preloaded process as it gets created lazily and may interfere
|
|
// with process count assertions
|
|
await pushPref("dom.ipc.processPrelaunch.enabled", false);
|
|
// This preference helps destroying the content process when we close the tab
|
|
await pushPref("dom.ipc.keepProcessesAlive.web", 1);
|
|
|
|
info("## Test with bfcache in parent DISABLED");
|
|
await pushPref("fission.bfcacheInParent", false);
|
|
await testTopLevelNavigations(false);
|
|
await testIframeNavigations(false);
|
|
await testTopLevelNavigationsOnDocumentWithIframe(false);
|
|
|
|
info("## Test with bfcache in parent ENABLED");
|
|
await pushPref("fission.bfcacheInParent", true);
|
|
await testTopLevelNavigations(true);
|
|
await testIframeNavigations(true);
|
|
await testTopLevelNavigationsOnDocumentWithIframe(true);
|
|
});
|
|
|
|
async function testTopLevelNavigations(bfcacheInParent) {
|
|
info(" # Test TOP LEVEL navigations");
|
|
// Create a TargetCommand for a given test tab
|
|
const tab = await addTab(TEST_COM_URL);
|
|
const commands = await CommandsFactory.forTab(tab);
|
|
const targetCommand = commands.targetCommand;
|
|
const { TYPES } = targetCommand;
|
|
|
|
await targetCommand.startListening();
|
|
|
|
// Assert that watchTargets will call the create callback for all existing frames
|
|
const targets = [];
|
|
const onAvailable = async ({ targetFront }) => {
|
|
is(
|
|
targetFront.targetType,
|
|
TYPES.FRAME,
|
|
"We are only notified about frame targets"
|
|
);
|
|
ok(targetFront.isTopLevel, "all targets of this test are top level");
|
|
targets.push(targetFront);
|
|
};
|
|
const destroyedTargets = [];
|
|
const onDestroyed = async ({ targetFront }) => {
|
|
is(
|
|
targetFront.targetType,
|
|
TYPES.FRAME,
|
|
"We are only notified about frame targets"
|
|
);
|
|
ok(targetFront.isTopLevel, "all targets of this test are top level");
|
|
destroyedTargets.push(targetFront);
|
|
};
|
|
|
|
await targetCommand.watchTargets({
|
|
types: [TYPES.FRAME],
|
|
onAvailable,
|
|
onDestroyed,
|
|
});
|
|
is(targets.length, 1, "retrieved only the top level target");
|
|
is(targets[0], targetCommand.targetFront, "the target is the top level one");
|
|
is(
|
|
destroyedTargets.length,
|
|
0,
|
|
"We get no destruction when calling watchTargets"
|
|
);
|
|
ok(
|
|
targets[0].targetForm.followWindowGlobalLifeCycle,
|
|
"the first server side target follows the WindowGlobal lifecycle, when server target switching is enabled"
|
|
);
|
|
|
|
// Navigate to the same page with query params
|
|
info("Load the second page");
|
|
const secondPageUrl = TEST_COM_URL + "?second-load";
|
|
const previousBrowsingContextID = gBrowser.selectedBrowser.browsingContext.id;
|
|
ok(
|
|
previousBrowsingContextID,
|
|
"Fetch the tab's browsing context id before navigation"
|
|
);
|
|
const onLoaded = BrowserTestUtils.browserLoaded(
|
|
gBrowser.selectedBrowser,
|
|
false,
|
|
secondPageUrl
|
|
);
|
|
BrowserTestUtils.startLoadingURIString(
|
|
gBrowser.selectedBrowser,
|
|
secondPageUrl
|
|
);
|
|
await onLoaded;
|
|
|
|
// Assert BrowsingContext changes as it impact the behavior of targets
|
|
if (bfcacheInParent) {
|
|
isnot(
|
|
previousBrowsingContextID,
|
|
gBrowser.selectedBrowser.browsingContext.id,
|
|
"When bfcacheInParent is enabled, same-origin navigations spawn new BrowsingContext"
|
|
);
|
|
} else {
|
|
is(
|
|
previousBrowsingContextID,
|
|
gBrowser.selectedBrowser.browsingContext.id,
|
|
"When bfcacheInParent is disabled, same-origin navigations re-use the same BrowsingContext"
|
|
);
|
|
}
|
|
|
|
// Same-origin navigations also spawn a new top level target
|
|
await waitFor(
|
|
() => targets.length == 2,
|
|
"wait for the next top level target"
|
|
);
|
|
is(
|
|
targets[1],
|
|
targetCommand.targetFront,
|
|
"the second target is the top level one"
|
|
);
|
|
// As targetFront.url isn't reliable and might be about:blank,
|
|
// try to assert that we got the right target via other means.
|
|
// outerWindowID should change when navigating to another process,
|
|
// while it would stay equal for in-process navigations.
|
|
is(
|
|
targets[1].outerWindowID,
|
|
gBrowser.selectedBrowser.outerWindowID,
|
|
"the second target is for the second page"
|
|
);
|
|
ok(
|
|
targets[1].targetForm.followWindowGlobalLifeCycle,
|
|
"the new server side target follows the WindowGlobal lifecycle"
|
|
);
|
|
ok(targets[0].isDestroyed(), "the first target is destroyed");
|
|
is(destroyedTargets.length, 1, "We get one target being destroyed...");
|
|
is(destroyedTargets[0], targets[0], "...and that's the first one");
|
|
|
|
// Go back to the first page, this should be a bfcache navigation, and,
|
|
// we should get a new target
|
|
info("Go back to the first page");
|
|
const onPageShow = BrowserTestUtils.waitForContentEvent(
|
|
gBrowser.selectedBrowser,
|
|
"pageshow"
|
|
);
|
|
gBrowser.selectedBrowser.goBack();
|
|
await onPageShow;
|
|
|
|
await waitFor(
|
|
() => targets.length == 3,
|
|
"wait for the next top level target"
|
|
);
|
|
is(
|
|
targets[2],
|
|
targetCommand.targetFront,
|
|
"the third target is the top level one"
|
|
);
|
|
// Here as this is revived from cache, the url should always be correct
|
|
is(targets[2].url, TEST_COM_URL, "the third target is for the first url");
|
|
ok(
|
|
targets[2].targetForm.followWindowGlobalLifeCycle,
|
|
"the third target for bfcache navigations is following the WindowGlobal lifecycle"
|
|
);
|
|
ok(targets[1].isDestroyed(), "the second target is destroyed");
|
|
is(
|
|
destroyedTargets.length,
|
|
2,
|
|
"We get one additional target being destroyed..."
|
|
);
|
|
is(destroyedTargets[1], targets[1], "...and that's the second one");
|
|
|
|
// Wait for full attach in order to having breaking any pending requests
|
|
// when navigating to another page and switching to new process and target.
|
|
await waitForAllTargetsToBeAttached(targetCommand);
|
|
|
|
// Go forward and resurect the second page, this should also be a bfcache navigation, and,
|
|
// get a new target.
|
|
info("Go forward to the second page");
|
|
|
|
// When a new target will be created, we need to wait until it's fully processed
|
|
// to avoid pending promises.
|
|
const onNewTargetProcessed = bfcacheInParent
|
|
? new Promise(resolve => {
|
|
targetCommand.on(
|
|
"processed-available-target",
|
|
function onProcessedAvailableTarget(targetFront) {
|
|
if (targetFront === targets[3]) {
|
|
resolve();
|
|
targetCommand.off(
|
|
"processed-available-target",
|
|
onProcessedAvailableTarget
|
|
);
|
|
}
|
|
}
|
|
);
|
|
})
|
|
: null;
|
|
|
|
gBrowser.selectedBrowser.goForward();
|
|
|
|
await waitFor(
|
|
() => targets.length == 4,
|
|
"wait for the next top level target"
|
|
);
|
|
is(
|
|
targets[3],
|
|
targetCommand.targetFront,
|
|
"the 4th target is the top level one"
|
|
);
|
|
// Same here, as the document is revived from the cache, the url should always be correct
|
|
is(targets[3].url, secondPageUrl, "the 4th target is for the second url");
|
|
ok(
|
|
targets[3].targetForm.followWindowGlobalLifeCycle,
|
|
"the 4th target for bfcache navigations is following the WindowGlobal lifecycle"
|
|
);
|
|
ok(targets[2].isDestroyed(), "the third target is destroyed");
|
|
is(
|
|
destroyedTargets.length,
|
|
3,
|
|
"We get one additional target being destroyed..."
|
|
);
|
|
is(destroyedTargets[2], targets[2], "...and that's the third one");
|
|
|
|
// Wait for full attach in order to having breaking any pending requests
|
|
// when navigating to another page and switching to new process and target.
|
|
await waitForAllTargetsToBeAttached(targetCommand);
|
|
await onNewTargetProcessed;
|
|
|
|
await waitForAllTargetsToBeAttached(targetCommand);
|
|
|
|
targetCommand.unwatchTargets({ types: [TYPES.FRAME], onAvailable });
|
|
|
|
BrowserTestUtils.removeTab(tab);
|
|
|
|
await commands.destroy();
|
|
}
|
|
|
|
async function testTopLevelNavigationsOnDocumentWithIframe() {
|
|
info(" # Test TOP LEVEL navigations on document with iframe");
|
|
// Create a TargetCommand for a given test tab
|
|
const tab =
|
|
await addTab(`https://example.com/document-builder.sjs?id=top&html=
|
|
<h1>Top level</h1>
|
|
<iframe src="${encodeURIComponent(
|
|
"https://example.com/document-builder.sjs?id=iframe&html=<h2>In iframe</h2>"
|
|
)}">
|
|
</iframe>`);
|
|
const getLocationIdParam = url =>
|
|
new URLSearchParams(new URL(url).search).get("id");
|
|
|
|
const commands = await CommandsFactory.forTab(tab);
|
|
const targetCommand = commands.targetCommand;
|
|
const { TYPES } = targetCommand;
|
|
|
|
await targetCommand.startListening();
|
|
|
|
// Assert that watchTargets will call the create callback for all existing frames
|
|
const targets = [];
|
|
const onAvailable = async ({ targetFront }) => {
|
|
is(
|
|
targetFront.targetType,
|
|
TYPES.FRAME,
|
|
"We are only notified about frame targets"
|
|
);
|
|
targets.push(targetFront);
|
|
};
|
|
const destroyedTargets = [];
|
|
const onDestroyed = async ({ targetFront }) => {
|
|
is(
|
|
targetFront.targetType,
|
|
TYPES.FRAME,
|
|
"We are only notified about frame targets"
|
|
);
|
|
destroyedTargets.push(targetFront);
|
|
};
|
|
|
|
await targetCommand.watchTargets({
|
|
types: [TYPES.FRAME],
|
|
onAvailable,
|
|
onDestroyed,
|
|
});
|
|
|
|
is(targets.length, 2, "retrieved targets for top level and iframe documents");
|
|
is(targets[0], targetCommand.targetFront, "the target is the top level one");
|
|
is(
|
|
getLocationIdParam(targets[1].url),
|
|
"iframe",
|
|
"the second target is the iframe one"
|
|
);
|
|
|
|
is(
|
|
destroyedTargets.length,
|
|
0,
|
|
"We get no destruction when calling watchTargets"
|
|
);
|
|
|
|
info("Navigate to a new page");
|
|
let targetCountBeforeNavigation = targets.length;
|
|
const secondPageUrl = `https://example.com/document-builder.sjs?html=second`;
|
|
const onLoaded = BrowserTestUtils.browserLoaded(
|
|
gBrowser.selectedBrowser,
|
|
false,
|
|
secondPageUrl
|
|
);
|
|
BrowserTestUtils.startLoadingURIString(
|
|
gBrowser.selectedBrowser,
|
|
secondPageUrl
|
|
);
|
|
await onLoaded;
|
|
|
|
// Same-origin navigations also spawn a new top level target
|
|
await waitFor(
|
|
() => targets.length == targetCountBeforeNavigation + 1,
|
|
"wait for the next top level target"
|
|
);
|
|
is(
|
|
targets.at(-1),
|
|
targetCommand.targetFront,
|
|
"the new target is the top level one"
|
|
);
|
|
|
|
ok(targets[0].isDestroyed(), "the first target is destroyed");
|
|
ok(targets[1].isDestroyed(), "the second target is destroyed");
|
|
is(destroyedTargets.length, 2, "The two targets were destroyed");
|
|
|
|
// Go back to the first page, this should be a bfcache navigation, and,
|
|
// we should get 2 new targets
|
|
targetCountBeforeNavigation = targets.length;
|
|
info("Go back to the first page");
|
|
gBrowser.selectedBrowser.goBack();
|
|
|
|
await waitFor(
|
|
() => targets.length === targetCountBeforeNavigation + 2,
|
|
"wait for the next targets"
|
|
);
|
|
|
|
await waitFor(() => targets.at(-2).url && targets.at(-1).url);
|
|
is(
|
|
getLocationIdParam(targets.at(-2).url),
|
|
"top",
|
|
"the first new target is for the top document…"
|
|
);
|
|
is(
|
|
getLocationIdParam(targets.at(-1).url),
|
|
"iframe",
|
|
"…and the second one is for the iframe"
|
|
);
|
|
|
|
ok(
|
|
targets[targetCountBeforeNavigation - 1].isDestroyed(),
|
|
"the target for the second page is destroyed"
|
|
);
|
|
is(
|
|
destroyedTargets.length,
|
|
targetCountBeforeNavigation,
|
|
"We get one additional target being destroyed…"
|
|
);
|
|
is(
|
|
destroyedTargets.at(-1),
|
|
targets[targetCountBeforeNavigation - 1],
|
|
"…and that's the second page one"
|
|
);
|
|
|
|
await waitForAllTargetsToBeAttached(targetCommand);
|
|
|
|
targetCommand.unwatchTargets({
|
|
types: [TYPES.FRAME],
|
|
onAvailable,
|
|
onDestroyed,
|
|
});
|
|
|
|
BrowserTestUtils.removeTab(tab);
|
|
|
|
await commands.destroy();
|
|
}
|
|
|
|
async function testIframeNavigations() {
|
|
info(" # Test IFRAME navigations");
|
|
// Create a TargetCommand for a given test tab
|
|
const tab = await addTab(
|
|
`https://example.org/document-builder.sjs?html=<iframe src="${TEST_COM_URL}"></iframe>`
|
|
);
|
|
const commands = await CommandsFactory.forTab(tab);
|
|
const targetCommand = commands.targetCommand;
|
|
const { TYPES } = targetCommand;
|
|
|
|
await targetCommand.startListening();
|
|
|
|
// Assert that watchTargets will call the create callback for all existing frames
|
|
const targets = [];
|
|
const onAvailable = async ({ targetFront }) => {
|
|
is(
|
|
targetFront.targetType,
|
|
TYPES.FRAME,
|
|
"We are only notified about frame targets"
|
|
);
|
|
targets.push(targetFront);
|
|
};
|
|
await targetCommand.watchTargets({ types: [TYPES.FRAME], onAvailable });
|
|
|
|
is(targets.length, 2, "retrieved the top level and the iframe targets");
|
|
is(
|
|
targets[0],
|
|
targetCommand.targetFront,
|
|
"the first target is the top level one"
|
|
);
|
|
is(targets[1].url, TEST_COM_URL, "the second target is the iframe one");
|
|
|
|
// Navigate to the same page with query params
|
|
info("Load the second page");
|
|
const secondPageUrl = TEST_COM_URL + "?second-load";
|
|
await SpecialPowers.spawn(
|
|
gBrowser.selectedBrowser,
|
|
[secondPageUrl],
|
|
function (url) {
|
|
const iframe = content.document.querySelector("iframe");
|
|
iframe.src = url;
|
|
}
|
|
);
|
|
|
|
await waitFor(() => targets.length == 3, "wait for the next target");
|
|
is(targets[2].url, secondPageUrl, "the second target is for the second url");
|
|
ok(targets[1].isDestroyed(), "the first target is destroyed");
|
|
|
|
// Go back to the first page, this should be a bfcache navigation, and,
|
|
// we should get a new target
|
|
info("Go back to the first page");
|
|
const iframeBrowsingContext = await SpecialPowers.spawn(
|
|
gBrowser.selectedBrowser,
|
|
[],
|
|
function () {
|
|
const iframe = content.document.querySelector("iframe");
|
|
return iframe.browsingContext;
|
|
}
|
|
);
|
|
await SpecialPowers.spawn(iframeBrowsingContext, [], function () {
|
|
content.history.back();
|
|
});
|
|
|
|
await waitFor(() => targets.length == 4, "wait for the next target");
|
|
is(targets[3].url, TEST_COM_URL, "the third target is for the first url");
|
|
ok(targets[2].isDestroyed(), "the second target is destroyed");
|
|
|
|
// Go forward and resurect the second page, this should also be a bfcache navigation, and,
|
|
// get a new target.
|
|
info("Go forward to the second page");
|
|
await SpecialPowers.spawn(iframeBrowsingContext, [], function () {
|
|
content.history.forward();
|
|
});
|
|
|
|
await waitFor(() => targets.length == 5, "wait for the next target");
|
|
is(targets[4].url, secondPageUrl, "the 4th target is for the second url");
|
|
ok(targets[3].isDestroyed(), "the third target is destroyed");
|
|
|
|
targetCommand.unwatchTargets({ types: [TYPES.FRAME], onAvailable });
|
|
|
|
await waitForAllTargetsToBeAttached(targetCommand);
|
|
|
|
BrowserTestUtils.removeTab(tab);
|
|
|
|
await commands.destroy();
|
|
}
|