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 );