tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

browser_target_command_bfcache.js (15049B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 // Test the TargetCommand API when bfcache navigations happen
      7 
      8 const TEST_COM_URL = URL_ROOT_SSL + "simple_document.html";
      9 
     10 add_task(async function () {
     11  // Disable the preloaded process as it gets created lazily and may interfere
     12  // with process count assertions
     13  await pushPref("dom.ipc.processPrelaunch.enabled", false);
     14  // This preference helps destroying the content process when we close the tab
     15  await pushPref("dom.ipc.keepProcessesAlive.web", 1);
     16 
     17  info("## Test with bfcache in parent DISABLED");
     18  await pushPref("fission.bfcacheInParent", false);
     19  await testTopLevelNavigations(false);
     20  await testIframeNavigations(false);
     21  await testTopLevelNavigationsOnDocumentWithIframe(false);
     22 
     23  // bfcacheInParent only works if sessionHistoryInParent is enable
     24  // so only test it if both settings are enabled.
     25  // (it looks like sessionHistoryInParent is enabled by default when fission is enabled)
     26  if (Services.appinfo.sessionHistoryInParent) {
     27    info("## Test with bfcache in parent ENABLED");
     28    await pushPref("fission.bfcacheInParent", true);
     29    await testTopLevelNavigations(true);
     30    await testIframeNavigations(true);
     31    await testTopLevelNavigationsOnDocumentWithIframe(true);
     32  }
     33 });
     34 
     35 async function testTopLevelNavigations(bfcacheInParent) {
     36  info(" # Test TOP LEVEL navigations");
     37  // Create a TargetCommand for a given test tab
     38  const tab = await addTab(TEST_COM_URL);
     39  const commands = await CommandsFactory.forTab(tab);
     40  const targetCommand = commands.targetCommand;
     41  const { TYPES } = targetCommand;
     42 
     43  await targetCommand.startListening();
     44 
     45  // Assert that watchTargets will call the create callback for all existing frames
     46  const targets = [];
     47  const onAvailable = async ({ targetFront }) => {
     48    is(
     49      targetFront.targetType,
     50      TYPES.FRAME,
     51      "We are only notified about frame targets"
     52    );
     53    ok(targetFront.isTopLevel, "all targets of this test are top level");
     54    targets.push(targetFront);
     55  };
     56  const destroyedTargets = [];
     57  const onDestroyed = async ({ targetFront }) => {
     58    is(
     59      targetFront.targetType,
     60      TYPES.FRAME,
     61      "We are only notified about frame targets"
     62    );
     63    ok(targetFront.isTopLevel, "all targets of this test are top level");
     64    destroyedTargets.push(targetFront);
     65  };
     66 
     67  await targetCommand.watchTargets({
     68    types: [TYPES.FRAME],
     69    onAvailable,
     70    onDestroyed,
     71  });
     72  is(targets.length, 1, "retrieved only the top level target");
     73  is(targets[0], targetCommand.targetFront, "the target is the top level one");
     74  is(
     75    destroyedTargets.length,
     76    0,
     77    "We get no destruction when calling watchTargets"
     78  );
     79  ok(
     80    targets[0].targetForm.followWindowGlobalLifeCycle,
     81    "the first server side target follows the WindowGlobal lifecycle, when server target switching is enabled"
     82  );
     83 
     84  // Navigate to the same page with query params
     85  info("Load the second page");
     86  const secondPageUrl = TEST_COM_URL + "?second-load";
     87  const previousBrowsingContextID = gBrowser.selectedBrowser.browsingContext.id;
     88  ok(
     89    previousBrowsingContextID,
     90    "Fetch the tab's browsing context id before navigation"
     91  );
     92  const onLoaded = BrowserTestUtils.browserLoaded(
     93    gBrowser.selectedBrowser,
     94    false,
     95    secondPageUrl
     96  );
     97  BrowserTestUtils.startLoadingURIString(
     98    gBrowser.selectedBrowser,
     99    secondPageUrl
    100  );
    101  await onLoaded;
    102 
    103  // Assert BrowsingContext changes as it impact the behavior of targets
    104  if (bfcacheInParent) {
    105    isnot(
    106      previousBrowsingContextID,
    107      gBrowser.selectedBrowser.browsingContext.id,
    108      "When bfcacheInParent is enabled, same-origin navigations spawn new BrowsingContext"
    109    );
    110  } else {
    111    is(
    112      previousBrowsingContextID,
    113      gBrowser.selectedBrowser.browsingContext.id,
    114      "When bfcacheInParent is disabled, same-origin navigations re-use the same BrowsingContext"
    115    );
    116  }
    117 
    118  // Same-origin navigations also spawn a new top level target
    119  await waitFor(
    120    () => targets.length == 2,
    121    "wait for the next top level target"
    122  );
    123  is(
    124    targets[1],
    125    targetCommand.targetFront,
    126    "the second target is the top level one"
    127  );
    128  // As targetFront.url isn't reliable and might be about:blank,
    129  // try to assert that we got the right target via other means.
    130  // outerWindowID should change when navigating to another process,
    131  // while it would stay equal for in-process navigations.
    132  is(
    133    targets[1].outerWindowID,
    134    gBrowser.selectedBrowser.outerWindowID,
    135    "the second target is for the second page"
    136  );
    137  ok(
    138    targets[1].targetForm.followWindowGlobalLifeCycle,
    139    "the new server side target follows the WindowGlobal lifecycle"
    140  );
    141  ok(targets[0].isDestroyed(), "the first target is destroyed");
    142  is(destroyedTargets.length, 1, "We get one target being destroyed...");
    143  is(destroyedTargets[0], targets[0], "...and that's the first one");
    144 
    145  // Go back to the first page, this should be a bfcache navigation, and,
    146  // we should get a new target
    147  info("Go back to the first page");
    148  const onPageShow = BrowserTestUtils.waitForContentEvent(
    149    gBrowser.selectedBrowser,
    150    "pageshow"
    151  );
    152  gBrowser.selectedBrowser.goBack();
    153  await onPageShow;
    154 
    155  await waitFor(
    156    () => targets.length == 3,
    157    "wait for the next top level target"
    158  );
    159  is(
    160    targets[2],
    161    targetCommand.targetFront,
    162    "the third target is the top level one"
    163  );
    164  // Here as this is revived from cache, the url should always be correct
    165  is(targets[2].url, TEST_COM_URL, "the third target is for the first url");
    166  ok(
    167    targets[2].targetForm.followWindowGlobalLifeCycle,
    168    "the third target for bfcache navigations is following the WindowGlobal lifecycle"
    169  );
    170  ok(targets[1].isDestroyed(), "the second target is destroyed");
    171  is(
    172    destroyedTargets.length,
    173    2,
    174    "We get one additional target being destroyed..."
    175  );
    176  is(destroyedTargets[1], targets[1], "...and that's the second one");
    177 
    178  // Wait for full attach in order to having breaking any pending requests
    179  // when navigating to another page and switching to new process and target.
    180  await waitForAllTargetsToBeAttached(targetCommand);
    181 
    182  // Go forward and resurect the second page, this should also be a bfcache navigation, and,
    183  // get a new target.
    184  info("Go forward to the second page");
    185 
    186  // When a new target will be created, we need to wait until it's fully processed
    187  // to avoid pending promises.
    188  const onNewTargetProcessed = bfcacheInParent
    189    ? new Promise(resolve => {
    190        targetCommand.on(
    191          "processed-available-target",
    192          function onProcessedAvailableTarget(targetFront) {
    193            if (targetFront === targets[3]) {
    194              resolve();
    195              targetCommand.off(
    196                "processed-available-target",
    197                onProcessedAvailableTarget
    198              );
    199            }
    200          }
    201        );
    202      })
    203    : null;
    204 
    205  gBrowser.selectedBrowser.goForward();
    206 
    207  await waitFor(
    208    () => targets.length == 4,
    209    "wait for the next top level target"
    210  );
    211  is(
    212    targets[3],
    213    targetCommand.targetFront,
    214    "the 4th target is the top level one"
    215  );
    216  // Same here, as the document is revived from the cache, the url should always be correct
    217  is(targets[3].url, secondPageUrl, "the 4th target is for the second url");
    218  ok(
    219    targets[3].targetForm.followWindowGlobalLifeCycle,
    220    "the 4th target for bfcache navigations is following the WindowGlobal lifecycle"
    221  );
    222  ok(targets[2].isDestroyed(), "the third target is destroyed");
    223  is(
    224    destroyedTargets.length,
    225    3,
    226    "We get one additional target being destroyed..."
    227  );
    228  is(destroyedTargets[2], targets[2], "...and that's the third one");
    229 
    230  // Wait for full attach in order to having breaking any pending requests
    231  // when navigating to another page and switching to new process and target.
    232  await waitForAllTargetsToBeAttached(targetCommand);
    233  await onNewTargetProcessed;
    234 
    235  await waitForAllTargetsToBeAttached(targetCommand);
    236 
    237  targetCommand.unwatchTargets({ types: [TYPES.FRAME], onAvailable });
    238 
    239  BrowserTestUtils.removeTab(tab);
    240 
    241  await commands.destroy();
    242 }
    243 
    244 async function testTopLevelNavigationsOnDocumentWithIframe() {
    245  info(" # Test TOP LEVEL navigations on document with iframe");
    246  // Create a TargetCommand for a given test tab
    247  const tab =
    248    await addTab(`https://example.com/document-builder.sjs?id=top&html=
    249    <h1>Top level</h1>
    250    <iframe src="${encodeURIComponent(
    251      "https://example.com/document-builder.sjs?id=iframe&html=<h2>In iframe</h2>"
    252    )}">
    253    </iframe>`);
    254  const getLocationIdParam = url =>
    255    new URLSearchParams(new URL(url).search).get("id");
    256 
    257  const commands = await CommandsFactory.forTab(tab);
    258  const targetCommand = commands.targetCommand;
    259  const { TYPES } = targetCommand;
    260 
    261  await targetCommand.startListening();
    262 
    263  // Assert that watchTargets will call the create callback for all existing frames
    264  const targets = [];
    265  const onAvailable = async ({ targetFront }) => {
    266    is(
    267      targetFront.targetType,
    268      TYPES.FRAME,
    269      "We are only notified about frame targets"
    270    );
    271    targets.push(targetFront);
    272  };
    273  const destroyedTargets = [];
    274  const onDestroyed = async ({ targetFront }) => {
    275    is(
    276      targetFront.targetType,
    277      TYPES.FRAME,
    278      "We are only notified about frame targets"
    279    );
    280    destroyedTargets.push(targetFront);
    281  };
    282 
    283  await targetCommand.watchTargets({
    284    types: [TYPES.FRAME],
    285    onAvailable,
    286    onDestroyed,
    287  });
    288 
    289  is(targets.length, 2, "retrieved targets for top level and iframe documents");
    290  is(targets[0], targetCommand.targetFront, "the target is the top level one");
    291  is(
    292    getLocationIdParam(targets[1].url),
    293    "iframe",
    294    "the second target is the iframe one"
    295  );
    296 
    297  is(
    298    destroyedTargets.length,
    299    0,
    300    "We get no destruction when calling watchTargets"
    301  );
    302 
    303  info("Navigate to a new page");
    304  let targetCountBeforeNavigation = targets.length;
    305  const secondPageUrl = `https://example.com/document-builder.sjs?html=second`;
    306  const onLoaded = BrowserTestUtils.browserLoaded(
    307    gBrowser.selectedBrowser,
    308    false,
    309    secondPageUrl
    310  );
    311  BrowserTestUtils.startLoadingURIString(
    312    gBrowser.selectedBrowser,
    313    secondPageUrl
    314  );
    315  await onLoaded;
    316 
    317  // Same-origin navigations also spawn a new top level target
    318  await waitFor(
    319    () => targets.length == targetCountBeforeNavigation + 1,
    320    "wait for the next top level target"
    321  );
    322  is(
    323    targets.at(-1),
    324    targetCommand.targetFront,
    325    "the new target is the top level one"
    326  );
    327 
    328  ok(targets[0].isDestroyed(), "the first target is destroyed");
    329  ok(targets[1].isDestroyed(), "the second target is destroyed");
    330  is(destroyedTargets.length, 2, "The two targets were destroyed");
    331 
    332  // Go back to the first page, this should be a bfcache navigation, and,
    333  // we should get 2 new targets
    334  targetCountBeforeNavigation = targets.length;
    335  info("Go back to the first page");
    336  gBrowser.selectedBrowser.goBack();
    337 
    338  await waitFor(
    339    () => targets.length === targetCountBeforeNavigation + 2,
    340    "wait for the next targets"
    341  );
    342 
    343  await waitFor(() => targets.at(-2).url && targets.at(-1).url);
    344  is(
    345    getLocationIdParam(targets.at(-2).url),
    346    "top",
    347    "the first new target is for the top document…"
    348  );
    349  is(
    350    getLocationIdParam(targets.at(-1).url),
    351    "iframe",
    352    "…and the second one is for the iframe"
    353  );
    354 
    355  ok(
    356    targets[targetCountBeforeNavigation - 1].isDestroyed(),
    357    "the target for the second page is destroyed"
    358  );
    359  is(
    360    destroyedTargets.length,
    361    targetCountBeforeNavigation,
    362    "We get one additional target being destroyed…"
    363  );
    364  is(
    365    destroyedTargets.at(-1),
    366    targets[targetCountBeforeNavigation - 1],
    367    "…and that's the second page one"
    368  );
    369 
    370  await waitForAllTargetsToBeAttached(targetCommand);
    371 
    372  targetCommand.unwatchTargets({
    373    types: [TYPES.FRAME],
    374    onAvailable,
    375    onDestroyed,
    376  });
    377 
    378  BrowserTestUtils.removeTab(tab);
    379 
    380  await commands.destroy();
    381 }
    382 
    383 async function testIframeNavigations() {
    384  info(" # Test IFRAME navigations");
    385  // Create a TargetCommand for a given test tab
    386  const tab = await addTab(
    387    `https://example.org/document-builder.sjs?html=<iframe src="${TEST_COM_URL}"></iframe>`
    388  );
    389  const commands = await CommandsFactory.forTab(tab);
    390  const targetCommand = commands.targetCommand;
    391  const { TYPES } = targetCommand;
    392 
    393  await targetCommand.startListening();
    394 
    395  // Assert that watchTargets will call the create callback for all existing frames
    396  const targets = [];
    397  const onAvailable = async ({ targetFront }) => {
    398    is(
    399      targetFront.targetType,
    400      TYPES.FRAME,
    401      "We are only notified about frame targets"
    402    );
    403    targets.push(targetFront);
    404  };
    405  await targetCommand.watchTargets({ types: [TYPES.FRAME], onAvailable });
    406 
    407  is(targets.length, 2, "retrieved the top level and the iframe targets");
    408  is(
    409    targets[0],
    410    targetCommand.targetFront,
    411    "the first target is the top level one"
    412  );
    413  is(targets[1].url, TEST_COM_URL, "the second target is the iframe one");
    414 
    415  // Navigate to the same page with query params
    416  info("Load the second page");
    417  const secondPageUrl = TEST_COM_URL + "?second-load";
    418  await SpecialPowers.spawn(
    419    gBrowser.selectedBrowser,
    420    [secondPageUrl],
    421    function (url) {
    422      const iframe = content.document.querySelector("iframe");
    423      iframe.src = url;
    424    }
    425  );
    426 
    427  await waitFor(() => targets.length == 3, "wait for the next target");
    428  is(targets[2].url, secondPageUrl, "the second target is for the second url");
    429  ok(targets[1].isDestroyed(), "the first target is destroyed");
    430 
    431  // Go back to the first page, this should be a bfcache navigation, and,
    432  // we should get a new target
    433  info("Go back to the first page");
    434  const iframeBrowsingContext = await SpecialPowers.spawn(
    435    gBrowser.selectedBrowser,
    436    [],
    437    function () {
    438      const iframe = content.document.querySelector("iframe");
    439      return iframe.browsingContext;
    440    }
    441  );
    442  await SpecialPowers.spawn(iframeBrowsingContext, [], function () {
    443    content.history.back();
    444  });
    445 
    446  await waitFor(() => targets.length == 4, "wait for the next target");
    447  is(targets[3].url, TEST_COM_URL, "the third target is for the first url");
    448  ok(targets[2].isDestroyed(), "the second target is destroyed");
    449 
    450  // Go forward and resurect the second page, this should also be a bfcache navigation, and,
    451  // get a new target.
    452  info("Go forward to the second page");
    453  await SpecialPowers.spawn(iframeBrowsingContext, [], function () {
    454    content.history.forward();
    455  });
    456 
    457  await waitFor(() => targets.length == 5, "wait for the next target");
    458  is(targets[4].url, secondPageUrl, "the 4th target is for the second url");
    459  ok(targets[3].isDestroyed(), "the third target is destroyed");
    460 
    461  targetCommand.unwatchTargets({ types: [TYPES.FRAME], onAvailable });
    462 
    463  await waitForAllTargetsToBeAttached(targetCommand);
    464 
    465  BrowserTestUtils.removeTab(tab);
    466 
    467  await commands.destroy();
    468 }