/* 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); // 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.loadURIString(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"); gBrowser.selectedBrowser.goBack(); 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(bfcacheInParent) { 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=