Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

/* Any copyright is dedicated to the Public Domain.
"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);
// bfcacheInParent only works if sessionHistoryInParent is enable
// so only test it if both settings are enabled.
// (it looks like sessionHistoryInParent is enabled by default when fission is enabled)
if (Services.appinfo.sessionHistoryInParent) {
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 =
<h1>Top level</h1>
<iframe src="${encodeURIComponent(
)}">
</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,
});
if (isEveryFrameTargetEnabled()) {
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"
);
} else {
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"
);
info("Navigate to a new page");
let targetCountBeforeNavigation = targets.length;
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");
if (isEveryFrameTargetEnabled()) {
ok(targets[1].isDestroyed(), "the second target is destroyed");
is(destroyedTargets.length, 2, "The two targets were destroyed");
} else {
is(destroyedTargets.length, 1, "Only one target was destroyed");
}
// Go back to the first page, this should be a bfcache navigation, and,
// we should get a new target (or 2 if EFT is enabled)
targetCountBeforeNavigation = targets.length;
info("Go back to the first page");
gBrowser.selectedBrowser.goBack();
await waitFor(
() =>
targets.length ===
targetCountBeforeNavigation + (isEveryFrameTargetEnabled() ? 2 : 1),
"wait for the next top level target"
);
if (isEveryFrameTargetEnabled()) {
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"
);
} else {
is(
getLocationIdParam(targets.at(-1).url),
"top",
"the new target is for the first url"
);
}
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(
`http://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 });
// When fission/EFT is off, there isn't much to test for iframes as they are debugged
// when the unique top level target
if (!isFissionEnabled() && !isEveryFrameTargetEnabled()) {
is(
targets.length,
1,
"Without fission/EFT, there is only the top level target"
);
await commands.destroy();
return;
}
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();
}