tor-browser

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

test_Navigate.js (29350B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 let { EventEmitter } = ChromeUtils.importESModule(
      6  "resource://gre/modules/EventEmitter.sys.mjs"
      7 );
      8 const { setTimeout } = ChromeUtils.importESModule(
      9  "resource://gre/modules/Timer.sys.mjs"
     10 );
     11 
     12 const {
     13  DEFAULT_UNLOAD_TIMEOUT,
     14  getUnloadTimeoutMultiplier,
     15  ProgressListener,
     16  waitForInitialNavigationCompleted,
     17 } = ChromeUtils.importESModule(
     18  "chrome://remote/content/shared/Navigate.sys.mjs"
     19 );
     20 
     21 const { isInitialDocument, isUncommittedInitialDocument } =
     22  ChromeUtils.importESModule(
     23    "chrome://remote/content/shared/messagehandler/transports/BrowsingContextUtils.sys.mjs"
     24  );
     25 
     26 const LOAD_FLAG_ERROR_PAGE = 0x10000;
     27 
     28 const CURRENT_URI = Services.io.newURI("http://foo.bar/");
     29 const INITIAL_URI = Services.io.newURI("about:blank");
     30 const TARGET_URI = Services.io.newURI("http://foo.cheese/");
     31 const TARGET_URI_ERROR_PAGE = Services.io.newURI("doesnotexist://");
     32 const TARGET_URI_WITH_HASH = Services.io.newURI("http://foo.cheese/#foo");
     33 const TARGET_URI_FROM_NAVIGATION_COMMITTED = Services.io.newURI(
     34  "http://foo.cheese/#bar"
     35 );
     36 
     37 function wait(time) {
     38  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
     39  return new Promise(resolve => setTimeout(resolve, time));
     40 }
     41 
     42 class MockRequest {
     43  constructor(uri) {
     44    this.originalURI = uri;
     45  }
     46 
     47  QueryInterface = ChromeUtils.generateQI(["nsIRequest", "nsIChannel"]);
     48 }
     49 
     50 class MockWebProgress {
     51  constructor(browsingContext) {
     52    this.browsingContext = browsingContext;
     53 
     54    this.documentRequest = null;
     55    this.isLoadingDocument = false;
     56    this.listener = null;
     57    this.loadType = 0;
     58    this.progressListenerRemoved = false;
     59  }
     60 
     61  addProgressListener(listener) {
     62    if (this.listener) {
     63      throw new Error("Cannot register listener twice");
     64    }
     65 
     66    this.listener = listener;
     67  }
     68 
     69  removeProgressListener(listener) {
     70    if (listener === this.listener) {
     71      this.listener = null;
     72      this.progressListenerRemoved = true;
     73    } else {
     74      throw new Error("Unknown listener");
     75    }
     76  }
     77 
     78  sendLocationChange(options = {}) {
     79    const { flag = 0 } = options;
     80 
     81    this.documentRequest = null;
     82 
     83    if (flag & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
     84      this.browsingContext.updateURI(TARGET_URI_WITH_HASH);
     85    } else if (flag & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) {
     86      this.browsingContext.updateURI(TARGET_URI_ERROR_PAGE, true);
     87    }
     88 
     89    this.listener?.onLocationChange(
     90      this,
     91      this.documentRequest,
     92      this.browsingContext.currentURI,
     93      flag
     94    );
     95 
     96    return new Promise(executeSoon);
     97  }
     98 
     99  sendStartState(options = {}) {
    100    const { coop = false, isInitial = false } = options;
    101 
    102    if (coop) {
    103      this.browsingContext = new MockTopContext(this);
    104    }
    105 
    106    if (!this.browsingContext.currentWindowGlobal) {
    107      this.browsingContext.currentWindowGlobal = {};
    108    }
    109 
    110    this.browsingContext.currentWindowGlobal.isInitialDocument = isInitial;
    111    // Start is sent for the initial about:blank if and only if we commit to it
    112    this.browsingContext.currentWindowGlobal.isUncommittedInitialDocument = false;
    113 
    114    this.isLoadingDocument = true;
    115    this.loadType = 0;
    116    const uri = isInitial ? INITIAL_URI : TARGET_URI;
    117    this.documentRequest = new MockRequest(uri);
    118 
    119    this.listener?.onStateChange(
    120      this,
    121      this.documentRequest,
    122      Ci.nsIWebProgressListener.STATE_START,
    123      0
    124    );
    125 
    126    return new Promise(executeSoon);
    127  }
    128 
    129  sendStopState(options = {}) {
    130    const { flag = 0, loadType = 0 } = options;
    131 
    132    this.browsingContext.currentURI = this.documentRequest.originalURI;
    133 
    134    this.isLoadingDocument = false;
    135    this.documentRequest = null;
    136 
    137    this.listener?.onStateChange(
    138      this,
    139      this.documentRequest,
    140      Ci.nsIWebProgressListener.STATE_STOP,
    141      flag
    142    );
    143 
    144    if (loadType & LOAD_FLAG_ERROR_PAGE) {
    145      this.loadType = 0x10000;
    146      return this.sendLocationChange({
    147        flag: Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE,
    148      });
    149    }
    150 
    151    return new Promise(executeSoon);
    152  }
    153 }
    154 
    155 class MockTopContext {
    156  constructor(webProgress = null) {
    157    this.currentURI = CURRENT_URI;
    158    this.currentWindowGlobal = {
    159      isInitialDocument: true,
    160      isUncommittedInitialDocument: true,
    161      documentURI: CURRENT_URI,
    162    };
    163    this.id = 7;
    164    this.top = this;
    165    this.webProgress = webProgress || new MockWebProgress(this);
    166  }
    167 
    168  updateURI(uri, isError = false) {
    169    this.currentURI = uri;
    170    if (isError) {
    171      this.currentWindowGlobal.documentURI = "about:neterror?e=errorMessage";
    172    } else {
    173      this.currentWindowGlobal.documentURI = uri;
    174    }
    175  }
    176 }
    177 
    178 add_task(
    179  async function test_waitForInitialNavigation_initialDocumentNoWindowGlobal() {
    180    const browsingContext = new MockTopContext();
    181    const webProgress = browsingContext.webProgress;
    182 
    183    // In some cases there might be no window global yet.
    184    delete browsingContext.currentWindowGlobal;
    185 
    186    ok(!webProgress.isLoadingDocument, "Document is not loading");
    187 
    188    // without window global, we'll wait for start and stop
    189    const navigated = waitForInitialNavigationCompleted(webProgress);
    190    await webProgress.sendStartState({ isInitial: true });
    191 
    192    ok(
    193      !(await hasPromiseResolved(navigated)),
    194      "waitForInitialNavigationCompleted has not resolved yet"
    195    );
    196 
    197    await webProgress.sendStopState();
    198    const { currentURI, targetURI } = await navigated;
    199 
    200    ok(!webProgress.isLoadingDocument, "Document is not loading");
    201    ok(
    202      webProgress.browsingContext.currentWindowGlobal.isInitialDocument,
    203      "Is initial document"
    204    );
    205    equal(
    206      currentURI.spec,
    207      INITIAL_URI.spec,
    208      "Expected current URI has been set"
    209    );
    210    equal(targetURI.spec, INITIAL_URI.spec, "Expected target URI has been set");
    211  }
    212 );
    213 
    214 add_task(
    215  async function test_waitForInitialNavigation_initialDocumentNotLoaded() {
    216    const browsingContext = new MockTopContext();
    217    const webProgress = browsingContext.webProgress;
    218 
    219    ok(!webProgress.isLoadingDocument, "Document is not loading");
    220    ok(
    221      isUncommittedInitialDocument(webProgress.browsingContext),
    222      "Document is uncommitted initial"
    223    );
    224 
    225    // for uncommitted initial, we'll wait for start and stop
    226    const navigated = waitForInitialNavigationCompleted(webProgress);
    227 
    228    await webProgress.sendStartState({ isInitial: true });
    229 
    230    ok(
    231      !(await hasPromiseResolved(navigated)),
    232      "waitForInitialNavigationCompleted has not resolved yet"
    233    );
    234 
    235    await webProgress.sendStopState();
    236    const { currentURI, targetURI } = await navigated;
    237 
    238    ok(!webProgress.isLoadingDocument, "Document is not loading");
    239    ok(
    240      webProgress.browsingContext.currentWindowGlobal.isInitialDocument,
    241      "Is initial document"
    242    );
    243    equal(
    244      currentURI.spec,
    245      INITIAL_URI.spec,
    246      "Expected current URI has been set"
    247    );
    248    equal(targetURI.spec, INITIAL_URI.spec, "Expected target URI has been set");
    249  }
    250 );
    251 
    252 add_task(
    253  async function test_waitForInitialNavigation_initialDocumentLoadingAndNoAdditionalLoad() {
    254    const browsingContext = new MockTopContext();
    255    const webProgress = browsingContext.webProgress;
    256 
    257    await webProgress.sendStartState({ isInitial: true });
    258    ok(webProgress.isLoadingDocument, "Document is loading");
    259 
    260    const navigated = waitForInitialNavigationCompleted(webProgress);
    261 
    262    ok(
    263      !(await hasPromiseResolved(navigated)),
    264      "waitForInitialNavigationCompleted has not resolved yet"
    265    );
    266 
    267    await webProgress.sendStopState();
    268    const { currentURI, targetURI } = await navigated;
    269 
    270    ok(!webProgress.isLoadingDocument, "Document is not loading");
    271    ok(
    272      webProgress.browsingContext.currentWindowGlobal.isInitialDocument,
    273      "Is initial document"
    274    );
    275    equal(
    276      currentURI.spec,
    277      INITIAL_URI.spec,
    278      "Expected current URI has been set"
    279    );
    280    equal(targetURI.spec, INITIAL_URI.spec, "Expected target URI has been set");
    281  }
    282 );
    283 
    284 add_task(
    285  async function test_waitForInitialNavigation_initialDocumentFinishedLoadingNoAdditionalLoad() {
    286    const browsingContext = new MockTopContext();
    287    const webProgress = browsingContext.webProgress;
    288 
    289    await webProgress.sendStartState({ isInitial: true });
    290    await webProgress.sendStopState();
    291 
    292    ok(!webProgress.isLoadingDocument, "Document is not loading");
    293    ok(isInitialDocument(webProgress.browsingContext), "Document is initial");
    294    ok(
    295      !isUncommittedInitialDocument(webProgress.browsingContext),
    296      "Document is uncommitted"
    297    );
    298 
    299    const navigated = waitForInitialNavigationCompleted(webProgress);
    300 
    301    ok(
    302      await hasPromiseResolved(navigated),
    303      "waitForInitialNavigationCompleted resolves immediately"
    304    );
    305 
    306    const { currentURI, targetURI } = await navigated;
    307 
    308    ok(!webProgress.isLoadingDocument, "Document is not loading");
    309    ok(
    310      webProgress.browsingContext.currentWindowGlobal.isInitialDocument,
    311      "Is initial document"
    312    );
    313    equal(
    314      currentURI.spec,
    315      INITIAL_URI.spec,
    316      "Expected current URI has been set"
    317    );
    318    equal(targetURI.spec, INITIAL_URI.spec, "Expected target URI has been set");
    319  }
    320 );
    321 
    322 add_task(
    323  async function test_waitForInitialNavigation_notInitialDocumentNotLoading() {
    324    const browsingContext = new MockTopContext();
    325    const webProgress = browsingContext.webProgress;
    326 
    327    ok(!webProgress.isLoadingDocument, "Document is not loading");
    328 
    329    const navigated = waitForInitialNavigationCompleted(webProgress);
    330    await webProgress.sendStartState({ isInitial: false });
    331 
    332    ok(
    333      !(await hasPromiseResolved(navigated)),
    334      "waitForInitialNavigationCompleted has not resolved yet"
    335    );
    336 
    337    await webProgress.sendStopState();
    338    const { currentURI, targetURI } = await navigated;
    339 
    340    ok(!webProgress.isLoadingDocument, "Document is not loading");
    341    ok(
    342      !browsingContext.currentWindowGlobal.isInitialDocument,
    343      "Is not initial document"
    344    );
    345    equal(
    346      currentURI.spec,
    347      TARGET_URI.spec,
    348      "Expected current URI has been set"
    349    );
    350    equal(targetURI.spec, TARGET_URI.spec, "Expected target URI has been set");
    351  }
    352 );
    353 
    354 add_task(
    355  async function test_waitForInitialNavigation_notInitialDocumentAlreadyLoading() {
    356    const browsingContext = new MockTopContext();
    357    const webProgress = browsingContext.webProgress;
    358 
    359    await webProgress.sendStartState({ isInitial: false });
    360    ok(webProgress.isLoadingDocument, "Document is loading");
    361 
    362    const navigated = waitForInitialNavigationCompleted(webProgress);
    363 
    364    ok(
    365      !(await hasPromiseResolved(navigated)),
    366      "waitForInitialNavigationCompleted has not resolved yet"
    367    );
    368 
    369    await webProgress.sendStopState();
    370    const { currentURI, targetURI } = await navigated;
    371 
    372    ok(!webProgress.isLoadingDocument, "Document is not loading");
    373    ok(
    374      !browsingContext.currentWindowGlobal.isInitialDocument,
    375      "Is not initial document"
    376    );
    377    equal(
    378      currentURI.spec,
    379      TARGET_URI.spec,
    380      "Expected current URI has been set"
    381    );
    382    equal(targetURI.spec, TARGET_URI.spec, "Expected target URI has been set");
    383  }
    384 );
    385 
    386 add_task(
    387  async function test_waitForInitialNavigation_notInitialDocumentFinishedLoading() {
    388    const browsingContext = new MockTopContext();
    389    const webProgress = browsingContext.webProgress;
    390 
    391    await webProgress.sendStartState({ isInitial: false });
    392    await webProgress.sendStopState();
    393 
    394    ok(!webProgress.isLoadingDocument, "Document is not loading");
    395 
    396    const { currentURI, targetURI } =
    397      await waitForInitialNavigationCompleted(webProgress);
    398 
    399    ok(!webProgress.isLoadingDocument, "Document is not loading");
    400    ok(
    401      !webProgress.browsingContext.currentWindowGlobal.isInitialDocument,
    402      "Is not initial document"
    403    );
    404    equal(
    405      currentURI.spec,
    406      TARGET_URI.spec,
    407      "Expected current URI has been set"
    408    );
    409    equal(targetURI.spec, TARGET_URI.spec, "Expected target URI has been set");
    410  }
    411 );
    412 
    413 add_task(async function test_waitForInitialNavigation_resolveWhenStarted() {
    414  const browsingContext = new MockTopContext();
    415  const webProgress = browsingContext.webProgress;
    416 
    417  await webProgress.sendStartState({ isInitial: true });
    418  ok(webProgress.isLoadingDocument, "Document is already loading");
    419 
    420  const { currentURI, targetURI } = await waitForInitialNavigationCompleted(
    421    webProgress,
    422    {
    423      resolveWhenStarted: true,
    424    }
    425  );
    426 
    427  ok(webProgress.isLoadingDocument, "Document is still loading");
    428  ok(
    429    webProgress.browsingContext.currentWindowGlobal.isInitialDocument,
    430    "Is initial document"
    431  );
    432  equal(currentURI.spec, CURRENT_URI.spec, "Expected current URI has been set");
    433  equal(targetURI.spec, INITIAL_URI.spec, "Expected target URI has been set");
    434 });
    435 
    436 add_task(async function test_waitForInitialNavigation_crossOrigin() {
    437  const browsingContext = new MockTopContext();
    438  const webProgress = browsingContext.webProgress;
    439 
    440  ok(!webProgress.isLoadingDocument, "Document is not loading");
    441 
    442  const navigated = waitForInitialNavigationCompleted(webProgress);
    443  await webProgress.sendStartState({ coop: true });
    444 
    445  ok(
    446    !(await hasPromiseResolved(navigated)),
    447    "waitForInitialNavigationCompleted has not resolved yet"
    448  );
    449 
    450  await webProgress.sendStopState();
    451  const { currentURI, targetURI } = await navigated;
    452 
    453  notEqual(
    454    browsingContext,
    455    webProgress.browsingContext,
    456    "Got new browsing context"
    457  );
    458  ok(!webProgress.isLoadingDocument, "Document is not loading");
    459  ok(
    460    !webProgress.browsingContext.currentWindowGlobal.isInitialDocument,
    461    "Is not initial document"
    462  );
    463  equal(currentURI.spec, TARGET_URI.spec, "Expected current URI has been set");
    464  equal(targetURI.spec, TARGET_URI.spec, "Expected target URI has been set");
    465 });
    466 
    467 add_task(async function test_waitForInitialNavigation_unloadTimeout_default() {
    468  const browsingContext = new MockTopContext();
    469  const webProgress = browsingContext.webProgress;
    470 
    471  // Document starts out as uncommitted initial and not loading
    472  ok(!webProgress.isLoadingDocument, "Document is not loading");
    473 
    474  const navigated = waitForInitialNavigationCompleted(webProgress);
    475 
    476  // Start a timer longer than the timeout which will be used by
    477  // waitForInitialNavigationCompleted, and check that navigated resolves first.
    478  const waitForMoreThanDefaultTimeout = wait(
    479    DEFAULT_UNLOAD_TIMEOUT * 1.5 * getUnloadTimeoutMultiplier()
    480  );
    481  await Promise.race([navigated, waitForMoreThanDefaultTimeout]);
    482 
    483  ok(
    484    await hasPromiseResolved(navigated),
    485    "waitForInitialNavigationCompleted has resolved"
    486  );
    487 
    488  ok(!webProgress.isLoadingDocument, "Document is not loading");
    489  ok(
    490    webProgress.browsingContext.currentWindowGlobal.isInitialDocument,
    491    "Document is still on the initial document"
    492  );
    493 });
    494 
    495 add_task(async function test_waitForInitialNavigation_unloadTimeout_longer() {
    496  const browsingContext = new MockTopContext();
    497  const webProgress = browsingContext.webProgress;
    498 
    499  // Document starts out as uncommitted initial and not loading
    500  ok(!webProgress.isLoadingDocument, "Document is not loading");
    501 
    502  const navigated = waitForInitialNavigationCompleted(webProgress, {
    503    unloadTimeout: DEFAULT_UNLOAD_TIMEOUT * 3,
    504  });
    505 
    506  // Start a timer longer than the default timeout of the Navigate module.
    507  // However here we used a custom timeout, so we expect that the navigation
    508  // will not be done yet by the time this timer is done.
    509  const waitForMoreThanDefaultTimeout = wait(
    510    DEFAULT_UNLOAD_TIMEOUT * 1.5 * getUnloadTimeoutMultiplier()
    511  );
    512  await Promise.race([navigated, waitForMoreThanDefaultTimeout]);
    513 
    514  // The promise should not have resolved because we didn't reached the custom
    515  // timeout which is 3 times the default one.
    516  ok(
    517    !(await hasPromiseResolved(navigated)),
    518    "waitForInitialNavigationCompleted has not resolved yet"
    519  );
    520 
    521  // The navigation should eventually resolve once we reach the custom timeout.
    522  await navigated;
    523 
    524  ok(!webProgress.isLoadingDocument, "Document is not loading");
    525  ok(
    526    webProgress.browsingContext.currentWindowGlobal.isInitialDocument,
    527    "Document is still on the initial document"
    528  );
    529 });
    530 
    531 add_task(async function test_ProgressListener_expectNavigation() {
    532  const browsingContext = new MockTopContext();
    533  const webProgress = browsingContext.webProgress;
    534 
    535  const progressListener = new ProgressListener(webProgress, {
    536    expectNavigation: true,
    537    unloadTimeout: 10,
    538  });
    539  const navigated = progressListener.start();
    540 
    541  // Wait for unloadTimeout to finish in case it started
    542  await wait(30);
    543 
    544  ok(!(await hasPromiseResolved(navigated)), "Listener has not resolved yet");
    545 
    546  await webProgress.sendStartState();
    547  await webProgress.sendStopState();
    548 
    549  ok(await hasPromiseResolved(navigated), "Listener has resolved");
    550 });
    551 
    552 add_task(
    553  async function test_ProgressListener_expectNavigation_initialDocument() {
    554    const browsingContext = new MockTopContext();
    555    const webProgress = browsingContext.webProgress;
    556 
    557    const progressListener = new ProgressListener(webProgress, {
    558      expectNavigation: true,
    559      unloadTimeout: 10,
    560    });
    561    const navigated = progressListener.start();
    562 
    563    ok(!(await hasPromiseResolved(navigated)), "Listener has not resolved yet");
    564 
    565    await webProgress.sendStartState({ isInitial: true });
    566    await webProgress.sendStopState();
    567 
    568    ok(await hasPromiseResolved(navigated), "Listener has resolved");
    569  }
    570 );
    571 
    572 add_task(async function test_ProgressListener_isStarted() {
    573  const browsingContext = new MockTopContext();
    574  const webProgress = browsingContext.webProgress;
    575 
    576  const progressListener = new ProgressListener(webProgress);
    577  ok(!progressListener.isStarted);
    578 
    579  progressListener.start();
    580  ok(progressListener.isStarted);
    581 
    582  progressListener.stop();
    583  ok(!progressListener.isStarted);
    584 });
    585 
    586 add_task(async function test_ProgressListener_notWaitForExplicitStart() {
    587  // Create a webprogress and start it before creating the progress listener.
    588  const browsingContext = new MockTopContext();
    589  const webProgress = browsingContext.webProgress;
    590  await webProgress.sendStartState();
    591 
    592  // Create the progress listener for a webprogress already in a navigation.
    593  const progressListener = new ProgressListener(webProgress, {
    594    waitForExplicitStart: false,
    595  });
    596  const navigated = progressListener.start();
    597 
    598  // Send stop state to complete the initial navigation
    599  await webProgress.sendStopState();
    600  ok(
    601    await hasPromiseResolved(navigated),
    602    "Listener has resolved after initial navigation"
    603  );
    604 });
    605 
    606 add_task(async function test_ProgressListener_waitForExplicitStart() {
    607  // Create a webprogress and start it before creating the progress listener.
    608  const browsingContext = new MockTopContext();
    609  const webProgress = browsingContext.webProgress;
    610  await webProgress.sendStartState();
    611 
    612  // Create the progress listener for a webprogress already in a navigation.
    613  const progressListener = new ProgressListener(webProgress, {
    614    waitForExplicitStart: true,
    615  });
    616  const navigated = progressListener.start();
    617 
    618  // Send stop state to complete the initial navigation
    619  await webProgress.sendStopState();
    620  ok(
    621    !(await hasPromiseResolved(navigated)),
    622    "Listener has not resolved after initial navigation"
    623  );
    624 
    625  // Start a new navigation
    626  await webProgress.sendStartState();
    627  ok(
    628    !(await hasPromiseResolved(navigated)),
    629    "Listener has not resolved after starting new navigation"
    630  );
    631 
    632  // Finish the new navigation
    633  await webProgress.sendStopState();
    634  ok(
    635    await hasPromiseResolved(navigated),
    636    "Listener resolved after finishing the new navigation"
    637  );
    638 });
    639 
    640 add_task(async function test_invalid_resolveWhenCommitted() {
    641  // Create a webprogress and start it before creating the progress listener.
    642  const browsingContext = new MockTopContext();
    643  const webProgress = browsingContext.webProgress;
    644  await webProgress.sendStartState();
    645 
    646  Assert.throws(
    647    () =>
    648      new ProgressListener(webProgress, {
    649        resolveWhenCommitted: true,
    650        resolveWhenStarted: true,
    651        navigationManager: {},
    652      }),
    653    /Cannot use both resolveWhenStarted and resolveWhenCommitted/,
    654    "Expected error was returned"
    655  );
    656 
    657  Assert.throws(
    658    () =>
    659      new ProgressListener(webProgress, {
    660        resolveWhenCommitted: true,
    661        navigationManager: null,
    662      }),
    663    /Cannot use resolveWhenCommitted without a navigationManager/,
    664    "Expected error was returned"
    665  );
    666 });
    667 
    668 class MockNavigationManager extends EventEmitter {}
    669 
    670 add_task(async function test_ProgressListener_resolveWhenCommitted() {
    671  // Create a webprogress and start it before creating the progress listener.
    672  const browsingContext = new MockTopContext();
    673  const webProgress = browsingContext.webProgress;
    674  await webProgress.sendStartState();
    675  const mockNavigationManager = new MockNavigationManager();
    676 
    677  // Create the progress listener for a webprogress already in a navigation.
    678  const progressListener = new ProgressListener(webProgress, {
    679    resolveWhenCommitted: true,
    680    navigationManager: mockNavigationManager,
    681  });
    682 
    683  // Setup two navigation ids, and start the progress listener with the first
    684  // one.
    685  const navigationId1 = "navigationId1";
    686  const navigationId2 = "navigationId2";
    687 
    688  const navigated = progressListener.start(navigationId1);
    689 
    690  // Start a new navigation
    691  await webProgress.sendStartState();
    692  ok(
    693    !(await hasPromiseResolved(navigated)),
    694    "Listener has not resolved after navigation has only started"
    695  );
    696 
    697  // Emit an unexpected navigation-committed for the other navigation id.
    698  mockNavigationManager.emit("navigation-committed", {
    699    navigationId: navigationId2,
    700    url: TARGET_URI_FROM_NAVIGATION_COMMITTED.spec,
    701  });
    702  ok(
    703    !(await hasPromiseResolved(navigated)),
    704    "Listener has not resolved after an unexpected navigation-committed"
    705  );
    706  notEqual(
    707    progressListener.targetURI.spec,
    708    TARGET_URI_FROM_NAVIGATION_COMMITTED.spec,
    709    "Expected target URI has not been set from unexpected navigation-committed"
    710  );
    711 
    712  // Emit the expected navigation-committed event.
    713  mockNavigationManager.emit("navigation-committed", {
    714    navigationId: navigationId1,
    715    url: TARGET_URI_FROM_NAVIGATION_COMMITTED.spec,
    716  });
    717  ok(
    718    await hasPromiseResolved(navigated),
    719    "Listener has resolved after receiving the correct navigation-committed"
    720  );
    721  equal(
    722    progressListener.targetURI.spec,
    723    TARGET_URI_FROM_NAVIGATION_COMMITTED.spec,
    724    "Expected target URI has been set from navigation-committed"
    725  );
    726 });
    727 
    728 add_task(
    729  async function test_ProgressListener_waitForExplicitStartAndResolveWhenStarted() {
    730    // Create a webprogress and start it before creating the progress listener.
    731    const browsingContext = new MockTopContext();
    732    const webProgress = browsingContext.webProgress;
    733    await webProgress.sendStartState();
    734 
    735    // Create the progress listener for a webprogress already in a navigation.
    736    const progressListener = new ProgressListener(webProgress, {
    737      resolveWhenStarted: true,
    738      waitForExplicitStart: true,
    739    });
    740    const navigated = progressListener.start();
    741 
    742    // Send stop state to complete the initial navigation
    743    await webProgress.sendStopState();
    744    ok(
    745      !(await hasPromiseResolved(navigated)),
    746      "Listener has not resolved after initial navigation"
    747    );
    748 
    749    // Start a new navigation
    750    await webProgress.sendStartState();
    751    ok(
    752      await hasPromiseResolved(navigated),
    753      "Listener resolved after starting the new navigation"
    754    );
    755  }
    756 );
    757 
    758 add_task(
    759  async function test_ProgressListener_resolveWhenNavigatingInsideDocument() {
    760    const browsingContext = new MockTopContext();
    761    const webProgress = browsingContext.webProgress;
    762 
    763    const progressListener = new ProgressListener(webProgress);
    764    const navigated = progressListener.start();
    765 
    766    ok(!(await hasPromiseResolved(navigated)), "Listener has not resolved");
    767 
    768    // Send hash change location change notification to complete the navigation
    769    await webProgress.sendLocationChange({
    770      flag: Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT,
    771    });
    772 
    773    ok(await hasPromiseResolved(navigated), "Listener has resolved");
    774 
    775    const { currentURI, targetURI } = progressListener;
    776    equal(
    777      currentURI.spec,
    778      TARGET_URI_WITH_HASH.spec,
    779      "Expected current URI has been set"
    780    );
    781    equal(
    782      targetURI.spec,
    783      TARGET_URI_WITH_HASH.spec,
    784      "Expected target URI has been set"
    785    );
    786  }
    787 );
    788 
    789 add_task(async function test_ProgressListener_ignoreCacheError() {
    790  const browsingContext = new MockTopContext();
    791  const webProgress = browsingContext.webProgress;
    792 
    793  const progressListener = new ProgressListener(webProgress);
    794  const navigated = progressListener.start();
    795 
    796  ok(!(await hasPromiseResolved(navigated)), "Listener has not resolved");
    797 
    798  await webProgress.sendStartState();
    799  await webProgress.sendStopState({
    800    flag: Cr.NS_ERROR_PARSED_DATA_CACHED,
    801  });
    802 
    803  ok(await hasPromiseResolved(navigated), "Listener has resolved");
    804 });
    805 
    806 add_task(async function test_ProgressListener_navigationRejectedOnErrorPage() {
    807  const browsingContext = new MockTopContext();
    808  const webProgress = browsingContext.webProgress;
    809 
    810  const progressListener = new ProgressListener(webProgress, {
    811    waitForExplicitStart: false,
    812  });
    813  const navigated = progressListener.start();
    814 
    815  await webProgress.sendStartState();
    816  await webProgress.sendLocationChange({
    817    flag:
    818      Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT |
    819      Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE,
    820  });
    821 
    822  ok(
    823    await hasPromiseRejected(navigated),
    824    "Listener has rejected in location change for error page"
    825  );
    826 });
    827 
    828 add_task(
    829  async function test_ProgressListener_navigationRejectedOnStopStateErrorPage() {
    830    const browsingContext = new MockTopContext();
    831    const webProgress = browsingContext.webProgress;
    832 
    833    const progressListener = new ProgressListener(webProgress, {
    834      waitForExplicitStart: false,
    835    });
    836    const navigated = progressListener.start();
    837 
    838    await webProgress.sendStartState();
    839    await webProgress.sendStopState({
    840      flag: Cr.NS_ERROR_MALWARE_URI,
    841      loadType: LOAD_FLAG_ERROR_PAGE,
    842    });
    843 
    844    ok(
    845      await hasPromiseRejected(navigated),
    846      "Listener has rejected in stop state for erroneous navigation"
    847    );
    848  }
    849 );
    850 
    851 add_task(
    852  async function test_ProgressListener_navigationRejectedOnStopStateAndAborted() {
    853    const browsingContext = new MockTopContext();
    854    const webProgress = browsingContext.webProgress;
    855 
    856    for (const flag of [Cr.NS_BINDING_ABORTED, Cr.NS_ERROR_ABORT]) {
    857      const progressListener = new ProgressListener(webProgress, {
    858        waitForExplicitStart: false,
    859      });
    860      const navigated = progressListener.start();
    861 
    862      await webProgress.sendStartState();
    863      await webProgress.sendStopState({ flag });
    864 
    865      ok(
    866        await hasPromiseRejected(navigated),
    867        "Listener has rejected in stop state for erroneous navigation"
    868      );
    869    }
    870  }
    871 );
    872 
    873 add_task(async function test_ProgressListener_stopIfStarted() {
    874  const browsingContext = new MockTopContext();
    875  const webProgress = browsingContext.webProgress;
    876 
    877  const progressListener = new ProgressListener(webProgress);
    878  const navigated = progressListener.start();
    879 
    880  progressListener.stopIfStarted();
    881  ok(!(await hasPromiseResolved(navigated)), "Listener has not resolved");
    882 
    883  await webProgress.sendStartState();
    884  progressListener.stopIfStarted();
    885  ok(await hasPromiseResolved(navigated), "Listener has resolved");
    886 });
    887 
    888 add_task(async function test_ProgressListener_stopIfStarted_alreadyStarted() {
    889  // Create an already navigating browsing context.
    890  const browsingContext = new MockTopContext();
    891  const webProgress = browsingContext.webProgress;
    892  await webProgress.sendStartState();
    893 
    894  // Create a progress listener which accepts already ongoing navigations.
    895  const progressListener = new ProgressListener(webProgress, {
    896    waitForExplicitStart: false,
    897  });
    898  const navigated = progressListener.start();
    899 
    900  // stopIfStarted should stop the listener because of the ongoing navigation.
    901  progressListener.stopIfStarted();
    902  ok(await hasPromiseResolved(navigated), "Listener has resolved");
    903 });
    904 
    905 add_task(
    906  async function test_ProgressListener_stopIfStarted_alreadyStarted_waitForExplicitStart() {
    907    // Create an already navigating browsing context.
    908    const browsingContext = new MockTopContext();
    909    const webProgress = browsingContext.webProgress;
    910    await webProgress.sendStartState();
    911 
    912    // Create a progress listener which rejects already ongoing navigations.
    913    const progressListener = new ProgressListener(webProgress, {
    914      waitForExplicitStart: true,
    915    });
    916    const navigated = progressListener.start();
    917 
    918    // stopIfStarted will not stop the listener for the existing navigation.
    919    progressListener.stopIfStarted();
    920    ok(!(await hasPromiseResolved(navigated)), "Listener has not resolved");
    921 
    922    // stopIfStarted will stop the listener when called after starting a new
    923    // navigation.
    924    await webProgress.sendStartState();
    925    progressListener.stopIfStarted();
    926    ok(await hasPromiseResolved(navigated), "Listener has resolved");
    927  }
    928 );