tor-browser

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

browser_resources_document_events.js (22223B)


      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 ResourceCommand API around DOCUMENT_EVENT
      7 
      8 add_task(async function () {
      9  await testDocumentEventResources();
     10  await testDocumentEventResourcesWithIgnoreExistingResources();
     11  await testDomCompleteWithOverloadedConsole();
     12  await testIframeNavigation();
     13  await testBfCacheNavigation();
     14  await testDomCompleteWithWindowStop();
     15  await testCrossOriginNavigation();
     16  await testDomCompleteWithOfflineDocument();
     17 });
     18 
     19 async function testDocumentEventResources() {
     20  info("Test ResourceCommand for DOCUMENT_EVENT");
     21 
     22  // Open a test tab
     23  const title = "DocumentEventsTitle";
     24  const url = `data:text/html,<title>${title}</title>Document Events`;
     25  const tab = await addTab(url);
     26 
     27  const listener = new ResourceListener();
     28  const { commands } = await initResourceCommand(tab);
     29 
     30  info(
     31    "Check whether the document events are fired correctly even when the document was already loaded"
     32  );
     33  const onLoadingAtInit = listener.once("dom-loading");
     34  const onInteractiveAtInit = listener.once("dom-interactive");
     35  const onCompleteAtInit = listener.once("dom-complete");
     36  await commands.resourceCommand.watchResources(
     37    [commands.resourceCommand.TYPES.DOCUMENT_EVENT],
     38    {
     39      onAvailable: parameters => listener.dispatch(parameters),
     40    }
     41  );
     42  await assertPromises(
     43    commands,
     44    // targetBeforeNavigation is only used when there is a will-navigate and a navigate, but there is none here
     45    null,
     46    // As we started watching on an already loaded document, and no navigation happened since we called watchResources,
     47    // we don't have any will-navigate event
     48    null,
     49    onLoadingAtInit,
     50    onInteractiveAtInit,
     51    onCompleteAtInit
     52  );
     53  ok(
     54    true,
     55    "Document events are fired even when the document was already loaded"
     56  );
     57  let domLoadingResource = await onLoadingAtInit;
     58 
     59  is(
     60    domLoadingResource.url,
     61    url,
     62    `resource ${domLoadingResource.name} has expected url`
     63  );
     64  is(
     65    domLoadingResource.title,
     66    undefined,
     67    `resource ${domLoadingResource.name} does not have a title property`
     68  );
     69 
     70  let domInteractiveResource = await onInteractiveAtInit;
     71  is(
     72    domInteractiveResource.url,
     73    url,
     74    `resource ${domInteractiveResource.name} has expected url`
     75  );
     76  is(
     77    domInteractiveResource.title,
     78    title,
     79    `resource ${domInteractiveResource.name} has expected title`
     80  );
     81  let domCompleteResource = await onCompleteAtInit;
     82  is(
     83    domCompleteResource.url,
     84    undefined,
     85    `resource ${domCompleteResource.name} does not have a url property`
     86  );
     87  is(
     88    domCompleteResource.title,
     89    undefined,
     90    `resource ${domCompleteResource.name} does not have a title property`
     91  );
     92 
     93  info("Check whether the document events are fired correctly when reloading");
     94  const onWillNavigate = listener.once("will-navigate");
     95  const onLoadingAtReloaded = listener.once("dom-loading");
     96  const onInteractiveAtReloaded = listener.once("dom-interactive");
     97  const onCompleteAtReloaded = listener.once("dom-complete");
     98  const targetBeforeNavigation = commands.targetCommand.targetFront;
     99  gBrowser.reloadTab(tab);
    100  await assertPromises(
    101    commands,
    102    targetBeforeNavigation,
    103    onWillNavigate,
    104    onLoadingAtReloaded,
    105    onInteractiveAtReloaded,
    106    onCompleteAtReloaded
    107  );
    108  ok(true, "Document events are fired after reloading");
    109 
    110  domLoadingResource = await onLoadingAtReloaded;
    111  is(
    112    domLoadingResource.url,
    113    url,
    114    `resource ${domLoadingResource.name} has expected url after reloading`
    115  );
    116  is(
    117    domLoadingResource.title,
    118    undefined,
    119    `resource ${domLoadingResource.name} does not have a title property after reloading`
    120  );
    121 
    122  domInteractiveResource = await onInteractiveAtInit;
    123  is(
    124    domInteractiveResource.url,
    125    url,
    126    `resource ${domInteractiveResource.name} has url property after reloading`
    127  );
    128  is(
    129    domInteractiveResource.title,
    130    title,
    131    `resource ${domInteractiveResource.name} has expected title after reloading`
    132  );
    133  domCompleteResource = await onCompleteAtInit;
    134  is(
    135    domCompleteResource.url,
    136    undefined,
    137    `resource ${domCompleteResource.name} does not have a url property after reloading`
    138  );
    139  is(
    140    domCompleteResource.title,
    141    undefined,
    142    `resource ${domCompleteResource.name} does not have a title property after reloading`
    143  );
    144 
    145  await commands.destroy();
    146 }
    147 
    148 async function testDocumentEventResourcesWithIgnoreExistingResources() {
    149  info("Test ignoreExistingResources option for DOCUMENT_EVENT");
    150 
    151  const tab = await addTab("data:text/html,Document Events");
    152 
    153  const { commands } = await initResourceCommand(tab);
    154 
    155  info("Check whether the existing document events will not be fired");
    156  const documentEvents = [];
    157  await commands.resourceCommand.watchResources(
    158    [commands.resourceCommand.TYPES.DOCUMENT_EVENT],
    159    {
    160      onAvailable: resources => documentEvents.push(...resources),
    161      ignoreExistingResources: true,
    162    }
    163  );
    164  is(documentEvents.length, 0, "Existing document events are not fired");
    165 
    166  info("Check whether the future document events are fired");
    167  const targetBeforeNavigation = commands.targetCommand.targetFront;
    168  gBrowser.reloadTab(tab);
    169  info(
    170    "Wait for will-navigate, dom-loading, dom-interactive and dom-complete events"
    171  );
    172  await waitFor(() => documentEvents.length === 4);
    173  assertEvents({ commands, targetBeforeNavigation, documentEvents });
    174 
    175  await commands.destroy();
    176 }
    177 
    178 async function testIframeNavigation() {
    179  info("Test iframe navigations for DOCUMENT_EVENT");
    180 
    181  const tab = await addTab(
    182    'https://example.com/document-builder.sjs?html=<iframe src="https://example.net/document-builder.sjs?html=net"></iframe>'
    183  );
    184  const secondPageUrl = "https://example.org/document-builder.sjs?html=org";
    185 
    186  const { commands } = await initResourceCommand(tab);
    187 
    188  let documentEvents = [];
    189  await commands.resourceCommand.watchResources(
    190    [commands.resourceCommand.TYPES.DOCUMENT_EVENT],
    191    {
    192      onAvailable: resources => documentEvents.push(...resources),
    193    }
    194  );
    195  is(
    196    documentEvents.length,
    197    6,
    198    "We get two targets and two sets of events: dom-loading, dom-interactive, dom-complete"
    199  );
    200  const [, iframeTarget] = await commands.targetCommand.getAllTargets([
    201    commands.targetCommand.TYPES.FRAME,
    202  ]);
    203  // Filter out each target events as their order to be random between the two targets
    204  const topTargetEvents = documentEvents.filter(
    205    r => r.targetFront == commands.targetCommand.targetFront
    206  );
    207  const iframeTargetEvents = documentEvents.filter(
    208    r => r.targetFront != commands.targetCommand.targetFront
    209  );
    210  assertEvents({
    211    commands,
    212    documentEvents: [null /* no will-navigate */, ...topTargetEvents],
    213  });
    214  assertEvents({
    215    commands,
    216    documentEvents: [null /* no will-navigate */, ...iframeTargetEvents],
    217    expectedTargetFront: iframeTarget,
    218  });
    219 
    220  info("Navigate the iframe to another process (if fission is enabled)");
    221  documentEvents = [];
    222  await SpecialPowers.spawn(
    223    gBrowser.selectedBrowser,
    224    [secondPageUrl],
    225    function (url) {
    226      const iframe = content.document.querySelector("iframe");
    227      iframe.src = url;
    228    }
    229  );
    230 
    231  await waitFor(() => documentEvents.length >= 3);
    232  is(
    233    documentEvents.length,
    234    3,
    235    "We switch to a new target and get: dom-loading, dom-interactive, dom-complete (but no will-navigate as that's only for the top BrowsingContext)"
    236  );
    237  const [, newIframeTarget] = await commands.targetCommand.getAllTargets([
    238    commands.targetCommand.TYPES.FRAME,
    239  ]);
    240  assertEvents({
    241    commands,
    242    targetBeforeNavigation: iframeTarget,
    243    documentEvents: [null /* no will-navigate */, ...documentEvents],
    244    expectedTargetFront: newIframeTarget,
    245    expectedNewURI: secondPageUrl,
    246  });
    247 
    248  await commands.destroy();
    249 }
    250 
    251 function isBfCacheInParentEnabled() {
    252  return (
    253    Services.appinfo.sessionHistoryInParent &&
    254    Services.prefs.getBoolPref("fission.bfcacheInParent", false)
    255  );
    256 }
    257 
    258 async function testBfCacheNavigation() {
    259  info("Test bfcache navigations for DOCUMENT_EVENT");
    260 
    261  info("Open a first document and navigate to a second one");
    262  const firstLocation = "data:text/html,<title>first</title>first page";
    263  const secondLocation = "data:text/html,<title>second</title>second page";
    264  const tab = await addTab(firstLocation);
    265  const onLoaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
    266  BrowserTestUtils.startLoadingURIString(
    267    gBrowser.selectedBrowser,
    268    secondLocation
    269  );
    270  await onLoaded;
    271 
    272  const { commands } = await initResourceCommand(tab);
    273 
    274  const documentEvents = [];
    275  await commands.resourceCommand.watchResources(
    276    [commands.resourceCommand.TYPES.DOCUMENT_EVENT],
    277    {
    278      onAvailable: resources => {
    279        documentEvents.push(...resources);
    280      },
    281      ignoreExistingResources: true,
    282    }
    283  );
    284  // Wait for some time for extra safety
    285  await wait(250);
    286  is(documentEvents.length, 0, "Existing document events are not fired");
    287 
    288  info("Navigate back to the first page");
    289  const onSwitched = commands.targetCommand.once("switched-target");
    290  const targetBeforeNavigation = commands.targetCommand.targetFront;
    291  gBrowser.goBack();
    292 
    293  if (isBfCacheInParentEnabled()) {
    294    await onSwitched;
    295  }
    296 
    297  info(
    298    "Wait for will-navigate, dom-loading, dom-interactive and dom-complete events"
    299  );
    300  await waitFor(() => documentEvents.length >= 4);
    301  /* Ignore will-navigate timestamp as all other DOCUMENT_EVENTS will be set at the original load date,
    302     which is when we loaded from the network, and not when we loaded from bfcache */
    303  assertEvents({
    304    commands,
    305    targetBeforeNavigation,
    306    documentEvents,
    307    ignoreWillNavigateTimestamp: true,
    308  });
    309 
    310  // Wait for some time in order to let a chance to have duplicated dom-loading events
    311  await wait(250);
    312 
    313  is(
    314    documentEvents.length,
    315    4,
    316    "There is no duplicated event and only the 4 expected DOCUMENT_EVENT states"
    317  );
    318  const [willNavigateEvent, loadingEvent, interactiveEvent, completeEvent] =
    319    documentEvents;
    320 
    321  is(
    322    willNavigateEvent.name,
    323    "will-navigate",
    324    "The first DOCUMENT_EVENT is will-navigate"
    325  );
    326  is(
    327    loadingEvent.name,
    328    "dom-loading",
    329    "The second DOCUMENT_EVENT is dom-loading"
    330  );
    331  is(
    332    interactiveEvent.name,
    333    "dom-interactive",
    334    "The third DOCUMENT_EVENT is dom-interactive"
    335  );
    336  is(
    337    completeEvent.name,
    338    "dom-complete",
    339    "The fourth DOCUMENT_EVENT is dom-complete"
    340  );
    341 
    342  is(
    343    loadingEvent.url,
    344    firstLocation,
    345    `resource ${loadingEvent.name} has expected url after navigation back`
    346  );
    347  is(
    348    loadingEvent.title,
    349    undefined,
    350    `resource ${loadingEvent.name} does not have a title property after navigating back`
    351  );
    352 
    353  is(
    354    interactiveEvent.url,
    355    firstLocation,
    356    `resource ${interactiveEvent.name} has expected url property after navigating back`
    357  );
    358  is(
    359    interactiveEvent.title,
    360    "first",
    361    `resource ${interactiveEvent.name} has expected title after navigating back`
    362  );
    363 
    364  is(
    365    completeEvent.url,
    366    undefined,
    367    `resource ${completeEvent.name} does not have a url property after navigating back`
    368  );
    369  is(
    370    completeEvent.title,
    371    undefined,
    372    `resource ${completeEvent.name} does not have a title property after navigating back`
    373  );
    374 
    375  await commands.destroy();
    376 }
    377 
    378 async function testCrossOriginNavigation() {
    379  info("Test cross origin navigations for DOCUMENT_EVENT");
    380 
    381  const tab = await addTab("https://example.com/document-builder.sjs?html=com");
    382 
    383  const { commands } = await initResourceCommand(tab);
    384 
    385  const documentEvents = [];
    386  await commands.resourceCommand.watchResources(
    387    [commands.resourceCommand.TYPES.DOCUMENT_EVENT],
    388    {
    389      onAvailable: resources => documentEvents.push(...resources),
    390      ignoreExistingResources: true,
    391    }
    392  );
    393  // Wait for some time for extra safety
    394  await wait(250);
    395  is(documentEvents.length, 0, "Existing document events are not fired");
    396 
    397  info("Navigate to another process");
    398  const onSwitched = commands.targetCommand.once("switched-target");
    399  const netUrl =
    400    "https://example.net/document-builder.sjs?html=<head><title>titleNet</title></head>net";
    401  const onLoaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
    402  const targetBeforeNavigation = commands.targetCommand.targetFront;
    403  BrowserTestUtils.startLoadingURIString(gBrowser.selectedBrowser, netUrl);
    404  await onLoaded;
    405  await onSwitched;
    406 
    407  info(
    408    "Wait for will-navigate, dom-loading, dom-interactive and dom-complete events"
    409  );
    410  await waitFor(() => documentEvents.length >= 4);
    411  assertEvents({ commands, targetBeforeNavigation, documentEvents });
    412 
    413  // Wait for some time in order to let a chance to have duplicated dom-loading events
    414  await wait(250);
    415 
    416  is(
    417    documentEvents.length,
    418    4,
    419    "There is no duplicated event and only the 4 expected DOCUMENT_EVENT states"
    420  );
    421  const [willNavigateEvent, loadingEvent, interactiveEvent, completeEvent] =
    422    documentEvents;
    423 
    424  is(
    425    willNavigateEvent.name,
    426    "will-navigate",
    427    "The first DOCUMENT_EVENT is will-navigate"
    428  );
    429  is(
    430    loadingEvent.name,
    431    "dom-loading",
    432    "The second DOCUMENT_EVENT is dom-loading"
    433  );
    434  is(
    435    interactiveEvent.name,
    436    "dom-interactive",
    437    "The third DOCUMENT_EVENT is dom-interactive"
    438  );
    439  is(
    440    completeEvent.name,
    441    "dom-complete",
    442    "The fourth DOCUMENT_EVENT is dom-complete"
    443  );
    444 
    445  is(
    446    loadingEvent.url,
    447    encodeURI(netUrl),
    448    `resource ${loadingEvent.name} has expected url after reloading`
    449  );
    450  is(
    451    loadingEvent.title,
    452    undefined,
    453    `resource ${loadingEvent.name} does not have a title property after reloading`
    454  );
    455 
    456  is(
    457    interactiveEvent.url,
    458    encodeURI(netUrl),
    459    `resource ${interactiveEvent.name} has expected url property after reloading`
    460  );
    461  is(
    462    interactiveEvent.title,
    463    "titleNet",
    464    `resource ${interactiveEvent.name} has expected title after reloading`
    465  );
    466 
    467  is(
    468    completeEvent.url,
    469    undefined,
    470    `resource ${completeEvent.name} does not have a url property after reloading`
    471  );
    472  is(
    473    completeEvent.title,
    474    undefined,
    475    `resource ${completeEvent.name} does not have a title property after reloading`
    476  );
    477 
    478  await commands.destroy();
    479 }
    480 
    481 async function testDomCompleteWithOverloadedConsole() {
    482  info("Test dom-complete with an overloaded console object");
    483 
    484  const tab = await addTab(
    485    "data:text/html,<script>window.console = {};</script>"
    486  );
    487 
    488  const { client, resourceCommand, targetCommand } =
    489    await initResourceCommand(tab);
    490 
    491  info("Check that all DOCUMENT_EVENTS are fired for the already loaded page");
    492  const documentEvents = [];
    493  await resourceCommand.watchResources([resourceCommand.TYPES.DOCUMENT_EVENT], {
    494    onAvailable: resources => documentEvents.push(...resources),
    495  });
    496  is(documentEvents.length, 3, "Existing document events are fired");
    497 
    498  const domComplete = documentEvents[2];
    499  is(domComplete.name, "dom-complete", "the last resource is the dom-complete");
    500  is(
    501    domComplete.hasNativeConsoleAPI,
    502    false,
    503    "the console object is reported to be overloaded"
    504  );
    505 
    506  targetCommand.destroy();
    507  await client.close();
    508 }
    509 
    510 async function testDomCompleteWithWindowStop() {
    511  info("Test dom-complete with a page calling window.stop()");
    512 
    513  const tab = await addTab("data:text/html,foo");
    514 
    515  const { commands, client, resourceCommand, targetCommand } =
    516    await initResourceCommand(tab);
    517 
    518  info("Check that all DOCUMENT_EVENTS are fired for the already loaded page");
    519  let documentEvents = [];
    520  await resourceCommand.watchResources([resourceCommand.TYPES.DOCUMENT_EVENT], {
    521    onAvailable: resources => documentEvents.push(...resources),
    522  });
    523  is(documentEvents.length, 3, "Existing document events are fired");
    524  documentEvents = [];
    525 
    526  const html = `<!DOCTYPE html><html>
    527  <head>
    528    <title>stopped page</title>
    529    <script>window.stop();</script>
    530  </head>
    531  <body>Page content that shouldn't be displayed</body>
    532 </html>`;
    533  const secondLocation = "data:text/html," + encodeURIComponent(html);
    534  const targetBeforeNavigation = commands.targetCommand.targetFront;
    535  BrowserTestUtils.startLoadingURIString(
    536    gBrowser.selectedBrowser,
    537    secondLocation
    538  );
    539  info(
    540    "Wait for will-navigate, dom-loading, dom-interactive and dom-complete events"
    541  );
    542  await waitFor(() => documentEvents.length === 4);
    543 
    544  assertEvents({ commands, targetBeforeNavigation, documentEvents });
    545 
    546  targetCommand.destroy();
    547  await client.close();
    548 }
    549 
    550 async function testDomCompleteWithOfflineDocument() {
    551  info("Test dom-complete with an offline page");
    552 
    553  const tab = await addTab(`${URL_ROOT_SSL}empty.html`);
    554 
    555  const { commands, client, resourceCommand, targetCommand } =
    556    await initResourceCommand(tab);
    557 
    558  info("Check that all DOCUMENT_EVENTS are fired for the already loaded page");
    559  let documentEvents = [];
    560  await resourceCommand.watchResources([resourceCommand.TYPES.DOCUMENT_EVENT], {
    561    onAvailable: resources => documentEvents.push(...resources),
    562  });
    563  is(documentEvents.length, 3, "Existing document events are fired");
    564  documentEvents = [];
    565 
    566  const targetBeforeNavigation = commands.targetCommand.targetFront;
    567  tab.linkedBrowser.browsingContext.forceOffline = true;
    568  gBrowser.reloadTab(tab);
    569 
    570  // The offline mode may break Document Event Watcher and we would miss some of the expected events
    571  info(
    572    "Wait for will-navigate, dom-loading, dom-interactive and dom-complete events"
    573  );
    574  await waitFor(() => documentEvents.length === 4);
    575 
    576  // Only will-navigate will have a valid timestamp, as the page is failing loading
    577  // and we get the offline notice page, the other events will be set to 0
    578  assertEvents({
    579    commands,
    580    targetBeforeNavigation,
    581    documentEvents,
    582    ignoreAllTimestamps: true,
    583  });
    584 
    585  targetCommand.destroy();
    586  await client.close();
    587 }
    588 
    589 async function assertPromises(
    590  commands,
    591  targetBeforeNavigation,
    592  onWillNavigate,
    593  onLoading,
    594  onInteractive,
    595  onComplete
    596 ) {
    597  const willNavigateEvent = await onWillNavigate;
    598  const loadingEvent = await onLoading;
    599  const interactiveEvent = await onInteractive;
    600  const completeEvent = await onComplete;
    601  assertEvents({
    602    commands,
    603    targetBeforeNavigation,
    604    documentEvents: [
    605      willNavigateEvent,
    606      loadingEvent,
    607      interactiveEvent,
    608      completeEvent,
    609    ],
    610  });
    611 }
    612 
    613 const isSlowPlatform =
    614  AppConstants.ASAN || AppConstants.DEBUG || AppConstants.TSAN;
    615 function assertEvents({
    616  commands,
    617  targetBeforeNavigation,
    618  documentEvents,
    619  expectedTargetFront = commands.targetCommand.targetFront,
    620  expectedNewURI = gBrowser.selectedBrowser.currentURI.spec,
    621  // The will-navigate timestamp has a hardcoded offset of 20ms to be "earlier"
    622  // than dom-content-loaded, but it frequently fails on debug/asan/tsan.
    623  // The observed failures are within < 5ms.
    624  ignoreWillNavigateTimestamp = isSlowPlatform,
    625  ignoreAllTimestamps = false,
    626 }) {
    627  const [willNavigateEvent, loadingEvent, interactiveEvent, completeEvent] =
    628    documentEvents;
    629  if (willNavigateEvent) {
    630    is(willNavigateEvent.name, "will-navigate", "Received the will-navigate");
    631    is(
    632      willNavigateEvent.newURI,
    633      expectedNewURI,
    634      "will-navigate newURI is set to the current tab new location"
    635    );
    636  }
    637  is(
    638    loadingEvent.name,
    639    "dom-loading",
    640    "loading received in the exepected order"
    641  );
    642  is(
    643    interactiveEvent.name,
    644    "dom-interactive",
    645    "interactive received in the expected order"
    646  );
    647  is(completeEvent.name, "dom-complete", "complete received last");
    648 
    649  if (willNavigateEvent) {
    650    is(
    651      typeof willNavigateEvent.time,
    652      "number",
    653      `Type of time attribute for will-navigate event is correct (${willNavigateEvent.time})`
    654    );
    655  }
    656  is(
    657    typeof loadingEvent.time,
    658    "number",
    659    `Type of time attribute for loading event is correct (${loadingEvent.time})`
    660  );
    661  is(
    662    typeof interactiveEvent.time,
    663    "number",
    664    `Type of time attribute for interactive event is correct (${interactiveEvent.time})`
    665  );
    666  is(
    667    typeof completeEvent.time,
    668    "number",
    669    `Type of time attribute for complete event is correct (${completeEvent.time})`
    670  );
    671 
    672  // In case of errors the timestamps may be set to 0.
    673  if (!ignoreAllTimestamps) {
    674    if (willNavigateEvent && !ignoreWillNavigateTimestamp) {
    675      Assert.lessOrEqual(
    676        willNavigateEvent.time,
    677        loadingEvent.time,
    678        `Timestamp for dom-loading event is greater than will-navigate event (${willNavigateEvent.time} <= ${loadingEvent.time})`
    679      );
    680    }
    681    Assert.lessOrEqual(
    682      loadingEvent.time,
    683      interactiveEvent.time,
    684      `Timestamp for interactive event is greater than loading event (${loadingEvent.time} <= ${interactiveEvent.time})`
    685    );
    686    Assert.lessOrEqual(
    687      interactiveEvent.time,
    688      completeEvent.time,
    689      `Timestamp for complete event is greater than interactive event (${interactiveEvent.time} <= ${completeEvent.time}).`
    690    );
    691  }
    692 
    693  if (willNavigateEvent) {
    694    // If we switched to a new target, this target will be different from currentTargetFront.
    695    // This only happen if we navigate to another process or if server target switching is enabled.
    696    is(
    697      willNavigateEvent.targetFront,
    698      targetBeforeNavigation,
    699      "will-navigate target was the one before the navigation"
    700    );
    701  }
    702  is(
    703    loadingEvent.targetFront,
    704    expectedTargetFront,
    705    "loading target is the expected one"
    706  );
    707  is(
    708    interactiveEvent.targetFront,
    709    expectedTargetFront,
    710    "interactive target is the expected one"
    711  );
    712  is(
    713    completeEvent.targetFront,
    714    expectedTargetFront,
    715    "complete target is the expected one"
    716  );
    717 
    718  is(
    719    completeEvent.hasNativeConsoleAPI,
    720    true,
    721    "None of the tests (except the dedicated one) overload the console object"
    722  );
    723 }
    724 
    725 class ResourceListener {
    726  _listeners = new Map();
    727 
    728  dispatch(resources) {
    729    for (const resource of resources) {
    730      const resolve = this._listeners.get(resource.name);
    731      if (resolve) {
    732        resolve(resource);
    733        this._listeners.delete(resource.name);
    734      }
    735    }
    736  }
    737 
    738  once(resourceName) {
    739    return new Promise(r => this._listeners.set(resourceName, r));
    740  }
    741 }