@angular/core 19.2.0 → 19.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/fesm2022/core.mjs +52 -69
  2. package/fesm2022/core.mjs.map +1 -1
  3. package/fesm2022/primitives/di.mjs +45 -0
  4. package/fesm2022/primitives/di.mjs.map +1 -0
  5. package/fesm2022/primitives/event-dispatch.mjs +2 -2
  6. package/fesm2022/primitives/signals.mjs +2 -2
  7. package/fesm2022/rxjs-interop.mjs +2 -2
  8. package/fesm2022/testing.mjs +280 -119
  9. package/fesm2022/testing.mjs.map +1 -1
  10. package/index.d.ts +27 -30
  11. package/package.json +5 -1
  12. package/primitives/di/index.d.ts +99 -0
  13. package/primitives/event-dispatch/index.d.ts +2 -2
  14. package/primitives/signals/index.d.ts +2 -2
  15. package/rxjs-interop/index.d.ts +2 -2
  16. package/schematics/bundles/{apply_import_manager-a4e62ded.js → apply_import_manager-b8d6885d.js} +4 -4
  17. package/schematics/bundles/{checker-2eecc677.js → checker-89987c98.js} +11 -15
  18. package/schematics/bundles/cleanup-unused-imports.js +8 -8
  19. package/schematics/bundles/{compiler_host-f313eac9.js → compiler_host-2398e4ca.js} +3 -3
  20. package/schematics/bundles/control-flow-migration.js +4 -4
  21. package/schematics/bundles/explicit-standalone-flag.js +6 -6
  22. package/schematics/bundles/{imports-31a38653.js → imports-047fbbc8.js} +2 -2
  23. package/schematics/bundles/{index-3891dd55.js → index-10911843.js} +5 -5
  24. package/schematics/bundles/{index-afc3f749.js → index-e0b2e4a7.js} +5 -5
  25. package/schematics/bundles/inject-migration.js +8 -8
  26. package/schematics/bundles/{leading_space-6e7a8ec6.js → leading_space-f8944434.js} +2 -2
  27. package/schematics/bundles/{migrate_ts_type_references-1abf1f5f.js → migrate_ts_type_references-52508cd4.js} +7 -7
  28. package/schematics/bundles/{ng_decorators-6878e227.js → ng_decorators-b0d8b324.js} +3 -3
  29. package/schematics/bundles/{nodes-ffdce442.js → nodes-7758dbf6.js} +2 -2
  30. package/schematics/bundles/output-migration.js +8 -8
  31. package/schematics/bundles/pending-tasks.js +6 -6
  32. package/schematics/bundles/{program-24da9092.js → program-0e1d4f10.js} +13 -13
  33. package/schematics/bundles/{project_paths-64bc3947.js → project_paths-c48796dd.js} +4 -4
  34. package/schematics/bundles/{project_tsconfig_paths-6c9cde78.js → project_tsconfig_paths-b558633b.js} +2 -2
  35. package/schematics/bundles/{property_name-42030525.js → property_name-ac18447e.js} +2 -2
  36. package/schematics/bundles/provide-initializer.js +6 -6
  37. package/schematics/bundles/route-lazy-loading.js +6 -6
  38. package/schematics/bundles/self-closing-tags-migration.js +44 -28
  39. package/schematics/bundles/signal-input-migration.js +10 -10
  40. package/schematics/bundles/signal-queries-migration.js +10 -10
  41. package/schematics/bundles/signals.js +10 -10
  42. package/schematics/bundles/standalone-migration.js +10 -10
  43. package/testing/index.d.ts +25 -14
@@ -1,6 +1,6 @@
1
1
  /**
2
- * @license Angular v19.2.0
3
- * (c) 2010-2024 Google LLC. https://angular.io/
2
+ * @license Angular v19.2.1
3
+ * (c) 2010-2025 Google LLC. https://angular.io/
4
4
  * License: MIT
5
5
  */
6
6
 
@@ -175,10 +175,10 @@ class TestBedApplicationErrorHandler {
175
175
  throw e;
176
176
  }
177
177
  }
178
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: TestBedApplicationErrorHandler, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
179
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: TestBedApplicationErrorHandler });
178
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: TestBedApplicationErrorHandler, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
179
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: TestBedApplicationErrorHandler });
180
180
  }
181
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: TestBedApplicationErrorHandler, decorators: [{
181
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.1", ngImport: i0, type: TestBedApplicationErrorHandler, decorators: [{
182
182
  type: Injectable
183
183
  }] });
184
184
 
@@ -2326,8 +2326,9 @@ class FakeNavigation {
2326
2326
  currentEntryIndex = 0;
2327
2327
  /**
2328
2328
  * The current navigate event.
2329
+ * @internal
2329
2330
  */
2330
- navigateEvent = undefined;
2331
+ navigateEvent = null;
2331
2332
  /**
2332
2333
  * A Map of pending traversals, so that traversals to the same entry can be
2333
2334
  * re-used.
@@ -2350,7 +2351,10 @@ class FakeNavigation {
2350
2351
  synchronousTraversals = false;
2351
2352
  /** Whether to allow a call to setInitialEntryForTesting. */
2352
2353
  canSetInitialEntry = true;
2353
- /** `EventTarget` to dispatch events. */
2354
+ /**
2355
+ * `EventTarget` to dispatch events.
2356
+ * @internal
2357
+ */
2354
2358
  eventTarget;
2355
2359
  /** The next unique id for created entries. Replace recreates this id. */
2356
2360
  nextId = 0;
@@ -2382,7 +2386,7 @@ class FakeNavigation {
2382
2386
  throw new Error('setInitialEntryForTesting can only be called before any ' + 'navigation has occurred');
2383
2387
  }
2384
2388
  const currentInitialEntry = this.entriesArr[0];
2385
- this.entriesArr[0] = new FakeNavigationHistoryEntry(new URL(url).toString(), {
2389
+ this.entriesArr[0] = new FakeNavigationHistoryEntry(this.window.document.createElement('div'), new URL(url).toString(), {
2386
2390
  index: 0,
2387
2391
  key: currentInitialEntry?.key ?? String(this.nextKey++),
2388
2392
  id: currentInitialEntry?.id ?? String(this.nextId++),
@@ -2430,7 +2434,7 @@ class FakeNavigation {
2430
2434
  sameDocument: hashChange,
2431
2435
  historyState: null,
2432
2436
  });
2433
- const result = new InternalNavigationResult();
2437
+ const result = new InternalNavigationResult(this);
2434
2438
  this.userAgentNavigate(destination, result, {
2435
2439
  navigationType,
2436
2440
  cancelable: true,
@@ -2462,7 +2466,7 @@ class FakeNavigation {
2462
2466
  sameDocument: true,
2463
2467
  historyState: data,
2464
2468
  });
2465
- const result = new InternalNavigationResult();
2469
+ const result = new InternalNavigationResult(this);
2466
2470
  this.userAgentNavigate(destination, result, {
2467
2471
  navigationType,
2468
2472
  cancelable: true,
@@ -2470,7 +2474,6 @@ class FakeNavigation {
2470
2474
  // Always false for pushState() or replaceState().
2471
2475
  userInitiated: false,
2472
2476
  hashChange,
2473
- skipPopState: true,
2474
2477
  });
2475
2478
  }
2476
2479
  /** Equivalent to `navigation.traverseTo()`. */
@@ -2512,11 +2515,11 @@ class FakeNavigation {
2512
2515
  sameDocument: entry.sameDocument,
2513
2516
  });
2514
2517
  this.prospectiveEntryIndex = entry.index;
2515
- const result = new InternalNavigationResult();
2518
+ const result = new InternalNavigationResult(this);
2516
2519
  this.traversalQueue.set(entry.key, result);
2517
2520
  this.runTraversal(() => {
2518
2521
  this.traversalQueue.delete(entry.key);
2519
- this.userAgentNavigate(destination, result, {
2522
+ const event = this.userAgentNavigate(destination, result, {
2520
2523
  navigationType: 'traverse',
2521
2524
  cancelable: true,
2522
2525
  canIntercept: true,
@@ -2525,6 +2528,8 @@ class FakeNavigation {
2525
2528
  hashChange,
2526
2529
  info: options?.info,
2527
2530
  });
2531
+ // Note this does not pay attention at all to the commit status of the event (and thus, does not support deferred commit for traversals)
2532
+ this.userAgentTraverse(event);
2528
2533
  });
2529
2534
  return {
2530
2535
  committed: result.committed,
@@ -2593,8 +2598,8 @@ class FakeNavigation {
2593
2598
  index: entry.index,
2594
2599
  sameDocument: entry.sameDocument,
2595
2600
  });
2596
- const result = new InternalNavigationResult();
2597
- this.userAgentNavigate(destination, result, {
2601
+ const result = new InternalNavigationResult(this);
2602
+ const event = this.userAgentNavigate(destination, result, {
2598
2603
  navigationType: 'traverse',
2599
2604
  cancelable: true,
2600
2605
  canIntercept: true,
@@ -2602,6 +2607,8 @@ class FakeNavigation {
2602
2607
  userInitiated: false,
2603
2608
  hashChange,
2604
2609
  });
2610
+ // Note this does not pay attention at all to the commit status of the event (and thus, does not support deferred commit for traversals)
2611
+ this.userAgentTraverse(event);
2605
2612
  });
2606
2613
  }
2607
2614
  /** Runs a traversal synchronously or asynchronously */
@@ -2652,9 +2659,9 @@ class FakeNavigation {
2652
2659
  this.canSetInitialEntry = false;
2653
2660
  if (this.navigateEvent) {
2654
2661
  this.navigateEvent.cancel(new DOMException('Navigation was aborted', 'AbortError'));
2655
- this.navigateEvent = undefined;
2662
+ this.navigateEvent = null;
2656
2663
  }
2657
- const navigateEvent = createFakeNavigateEvent({
2664
+ return dispatchNavigateEvent({
2658
2665
  navigationType: options.navigationType,
2659
2666
  cancelable: options.cancelable,
2660
2667
  canIntercept: options.canIntercept,
@@ -2664,78 +2671,103 @@ class FakeNavigation {
2664
2671
  destination,
2665
2672
  info: options.info,
2666
2673
  sameDocument: destination.sameDocument,
2667
- skipPopState: options.skipPopState,
2668
2674
  result,
2669
- userAgentCommit: () => {
2670
- this.userAgentCommit();
2671
- },
2672
2675
  });
2673
- this.navigateEvent = navigateEvent;
2674
- this.eventTarget.dispatchEvent(navigateEvent);
2675
- navigateEvent.dispatchedNavigateEvent();
2676
- if (navigateEvent.commitOption === 'immediate') {
2677
- navigateEvent.commit(/* internal= */ true);
2678
- }
2679
2676
  }
2680
- /** Implementation to commit a navigation. */
2681
- userAgentCommit() {
2682
- if (!this.navigateEvent) {
2683
- return;
2684
- }
2677
+ /**
2678
+ * Implementation to commit a navigation.
2679
+ * https://whatpr.org/html/10919/nav-history-apis.html#navigateevent-commit
2680
+ * @internal
2681
+ */
2682
+ commitNavigateEvent(navigateEvent) {
2683
+ navigateEvent.interceptionState = 'committed';
2685
2684
  const from = this.currentEntry;
2686
- if (!this.navigateEvent.sameDocument) {
2685
+ if (!from) {
2686
+ throw new Error('cannot commit navigation when current entry is null');
2687
+ }
2688
+ if (!navigateEvent.sameDocument) {
2687
2689
  const error = new Error('Cannot navigate to a non-same-document URL.');
2688
- this.navigateEvent.cancel(error);
2690
+ navigateEvent.cancel(error);
2689
2691
  throw error;
2690
2692
  }
2691
- if (this.navigateEvent.navigationType === 'push' ||
2692
- this.navigateEvent.navigationType === 'replace') {
2693
- this.userAgentPushOrReplace(this.navigateEvent.destination, {
2694
- navigationType: this.navigateEvent.navigationType,
2695
- });
2693
+ // "If navigationType is "push" or "replace", then run the URL and history update steps given document and event's destination's URL, with serialiedData set to event's classic history API state and historyHandling set to navigationType."
2694
+ if (navigateEvent.navigationType === 'push' || navigateEvent.navigationType === 'replace') {
2695
+ this.urlAndHistoryUpdateSteps(navigateEvent);
2696
2696
  }
2697
- else if (this.navigateEvent.navigationType === 'traverse') {
2698
- this.userAgentTraverse(this.navigateEvent.destination);
2697
+ else if (navigateEvent.navigationType === 'reload') {
2698
+ this.updateNavigationEntriesForSameDocumentNavigation(navigateEvent);
2699
2699
  }
2700
- this.navigateEvent.userAgentNavigated(this.currentEntry);
2701
- const currentEntryChangeEvent = createFakeNavigationCurrentEntryChangeEvent({
2702
- from,
2703
- navigationType: this.navigateEvent.navigationType,
2704
- });
2705
- this.eventTarget.dispatchEvent(currentEntryChangeEvent);
2706
- if (!this.navigateEvent.skipPopState) {
2707
- const popStateEvent = createPopStateEvent({
2708
- state: this.navigateEvent.destination.getHistoryState(),
2709
- });
2710
- this.window.dispatchEvent(popStateEvent);
2700
+ else if (navigateEvent.navigationType === 'traverse') {
2701
+ // "If navigationType is "traverse", then this event firing is happening as part of the traversal process, and that process will take care of performing the appropriate session history entry updates."
2711
2702
  }
2712
2703
  }
2713
- /** Implementation for a push or replace navigation. */
2714
- userAgentPushOrReplace(destination, { navigationType }) {
2715
- if (navigationType === 'push') {
2716
- this.currentEntryIndex++;
2717
- this.prospectiveEntryIndex = this.currentEntryIndex;
2718
- }
2719
- const index = this.currentEntryIndex;
2720
- const key = navigationType === 'push' ? String(this.nextKey++) : this.currentEntry.key;
2721
- const entry = new FakeNavigationHistoryEntry(destination.url, {
2722
- id: String(this.nextId++),
2723
- key,
2724
- index,
2725
- sameDocument: true,
2726
- state: destination.getState(),
2727
- historyState: destination.getHistoryState(),
2704
+ /**
2705
+ * Implementation for a push or replace navigation.
2706
+ * https://whatpr.org/html/10919/browsing-the-web.html#url-and-history-update-steps
2707
+ * https://whatpr.org/html/10919/nav-history-apis.html#update-the-navigation-api-entries-for-a-same-document-navigation
2708
+ */
2709
+ urlAndHistoryUpdateSteps(navigateEvent) {
2710
+ this.updateNavigationEntriesForSameDocumentNavigation(navigateEvent);
2711
+ }
2712
+ /**
2713
+ * Implementation for a traverse navigation.
2714
+ *
2715
+ * https://whatpr.org/html/10919/browsing-the-web.html#apply-the-traverse-history-step
2716
+ * ...
2717
+ * > Let updateDocument be an algorithm step which performs update document for history step application given targetEntry's document, targetEntry, changingNavigableContinuation's update-only, scriptHistoryLength, scriptHistoryIndex, navigationType, entriesForNavigationAPI, and previousEntry.
2718
+ * > If targetEntry's document is equal to displayedDocument, then perform updateDocument.
2719
+ * https://whatpr.org/html/10919/browsing-the-web.html#update-document-for-history-step-application
2720
+ * which then goes to https://whatpr.org/html/10919/nav-history-apis.html#update-the-navigation-api-entries-for-a-same-document-navigation
2721
+ */
2722
+ userAgentTraverse(navigateEvent) {
2723
+ this.updateNavigationEntriesForSameDocumentNavigation(navigateEvent);
2724
+ // Happens as part of "updating the document" steps https://whatpr.org/html/10919/browsing-the-web.html#updating-the-document
2725
+ const popStateEvent = createPopStateEvent({
2726
+ state: navigateEvent.destination.getHistoryState(),
2728
2727
  });
2729
- if (navigationType === 'push') {
2730
- this.entriesArr.splice(index, Infinity, entry);
2728
+ this.window.dispatchEvent(popStateEvent);
2729
+ // TODO(atscott): If oldURL's fragment is not equal to entry's URL's fragment, then queue a global task to fire an event named hashchange
2730
+ }
2731
+ /** https://whatpr.org/html/10919/nav-history-apis.html#update-the-navigation-api-entries-for-a-same-document-navigation */
2732
+ updateNavigationEntriesForSameDocumentNavigation({ destination, navigationType, result, }) {
2733
+ const oldCurrentNHE = this.currentEntry;
2734
+ const disposedNHEs = [];
2735
+ if (navigationType === 'traverse') {
2736
+ this.currentEntryIndex = destination.index;
2737
+ if (this.currentEntryIndex === -1) {
2738
+ throw new Error('unexpected current entry index');
2739
+ }
2731
2740
  }
2732
- else {
2733
- this.entriesArr[index] = entry;
2741
+ else if (navigationType === 'push') {
2742
+ this.currentEntryIndex++;
2743
+ this.prospectiveEntryIndex = this.currentEntryIndex; // prospectiveEntryIndex isn't in the spec but is an implementation detail
2744
+ disposedNHEs.push(...this.entriesArr.splice(this.currentEntryIndex));
2745
+ }
2746
+ else if (navigationType === 'replace') {
2747
+ disposedNHEs.push(oldCurrentNHE);
2748
+ }
2749
+ if (navigationType === 'push' || navigationType === 'replace') {
2750
+ const index = this.currentEntryIndex;
2751
+ const key = navigationType === 'push' ? String(this.nextKey++) : this.currentEntry.key;
2752
+ const newNHE = new FakeNavigationHistoryEntry(this.window.document.createElement('div'), destination.url, {
2753
+ id: String(this.nextId++),
2754
+ key,
2755
+ index,
2756
+ sameDocument: true,
2757
+ state: destination.getState(),
2758
+ historyState: destination.getHistoryState(),
2759
+ });
2760
+ this.entriesArr[this.currentEntryIndex] = newNHE;
2761
+ }
2762
+ result.committedResolve(this.currentEntry);
2763
+ const currentEntryChangeEvent = createFakeNavigationCurrentEntryChangeEvent({
2764
+ from: oldCurrentNHE,
2765
+ navigationType: navigationType,
2766
+ });
2767
+ this.eventTarget.dispatchEvent(currentEntryChangeEvent);
2768
+ for (const disposedNHE of disposedNHEs) {
2769
+ disposedNHE.dispose();
2734
2770
  }
2735
- }
2736
- /** Implementation for a traverse navigation. */
2737
- userAgentTraverse(destination) {
2738
- this.currentEntryIndex = destination.index;
2739
2771
  }
2740
2772
  /** Utility method for finding entries with the given `key`. */
2741
2773
  findEntry(key) {
@@ -2778,8 +2810,13 @@ class FakeNavigation {
2778
2810
  get onnavigateerror() {
2779
2811
  throw new Error('unimplemented');
2780
2812
  }
2813
+ _transition = null;
2814
+ /** @internal */
2815
+ set transition(t) {
2816
+ this._transition = t;
2817
+ }
2781
2818
  get transition() {
2782
- throw new Error('unimplemented');
2819
+ return this._transition;
2783
2820
  }
2784
2821
  updateCurrentEntry(_options) {
2785
2822
  throw new Error('unimplemented');
@@ -2792,6 +2829,7 @@ class FakeNavigation {
2792
2829
  * Fake equivalent of `NavigationHistoryEntry`.
2793
2830
  */
2794
2831
  class FakeNavigationHistoryEntry {
2832
+ eventTarget;
2795
2833
  url;
2796
2834
  sameDocument;
2797
2835
  id;
@@ -2801,7 +2839,8 @@ class FakeNavigationHistoryEntry {
2801
2839
  historyState;
2802
2840
  // tslint:disable-next-line:no-any
2803
2841
  ondispose = null;
2804
- constructor(url, { id, key, index, sameDocument, state, historyState, }) {
2842
+ constructor(eventTarget, url, { id, key, index, sameDocument, state, historyState, }) {
2843
+ this.eventTarget = eventTarget;
2805
2844
  this.url = url;
2806
2845
  this.id = id;
2807
2846
  this.key = key;
@@ -2821,21 +2860,34 @@ class FakeNavigationHistoryEntry {
2821
2860
  : this.historyState;
2822
2861
  }
2823
2862
  addEventListener(type, callback, options) {
2824
- throw new Error('unimplemented');
2863
+ this.eventTarget.addEventListener(type, callback, options);
2825
2864
  }
2826
2865
  removeEventListener(type, callback, options) {
2827
- throw new Error('unimplemented');
2866
+ this.eventTarget.removeEventListener(type, callback, options);
2828
2867
  }
2829
2868
  dispatchEvent(event) {
2830
- throw new Error('unimplemented');
2869
+ return this.eventTarget.dispatchEvent(event);
2870
+ }
2871
+ /** internal */
2872
+ dispose() {
2873
+ const disposeEvent = new Event('disposed');
2874
+ this.dispatchEvent(disposeEvent);
2875
+ // release current listeners
2876
+ this.eventTarget = null;
2831
2877
  }
2832
2878
  }
2833
2879
  /**
2834
2880
  * Create a fake equivalent of `NavigateEvent`. This is not a class because ES5
2835
2881
  * transpiled JavaScript cannot extend native Event.
2882
+ *
2883
+ * https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigate-event-firing
2836
2884
  */
2837
- function createFakeNavigateEvent({ cancelable, canIntercept, userInitiated, hashChange, navigationType, signal, destination, info, sameDocument, skipPopState, result, userAgentCommit, }) {
2885
+ function dispatchNavigateEvent({ cancelable, canIntercept, userInitiated, hashChange, navigationType, signal, destination, info, sameDocument, result, }) {
2886
+ const { navigation } = result;
2838
2887
  const event = new Event('navigate', { bubbles: false, cancelable });
2888
+ event.focusResetBehavior = null;
2889
+ event.scrollBehavior = null;
2890
+ event.interceptionState = 'none';
2839
2891
  event.canIntercept = canIntercept;
2840
2892
  event.userInitiated = userInitiated;
2841
2893
  event.hashChange = hashChange;
@@ -2845,71 +2897,151 @@ function createFakeNavigateEvent({ cancelable, canIntercept, userInitiated, hash
2845
2897
  event.info = info;
2846
2898
  event.downloadRequest = null;
2847
2899
  event.formData = null;
2900
+ event.result = result;
2848
2901
  event.sameDocument = sameDocument;
2849
- event.skipPopState = skipPopState;
2850
2902
  event.commitOption = 'immediate';
2851
- let handlerFinished = undefined;
2852
- let interceptCalled = false;
2903
+ let handlersFinished = [Promise.resolve()];
2853
2904
  let dispatchedNavigateEvent = false;
2854
- let commitCalled = false;
2855
2905
  event.intercept = function (options) {
2856
- interceptCalled = true;
2906
+ if (!this.canIntercept) {
2907
+ throw new DOMException(`Cannot intercept when canIntercept is 'false'`, 'SecurityError');
2908
+ }
2909
+ this.interceptionState = 'intercepted';
2857
2910
  event.sameDocument = true;
2858
2911
  const handler = options?.handler;
2859
2912
  if (handler) {
2860
- handlerFinished = handler();
2861
- }
2862
- if (options?.commit) {
2863
- event.commitOption = options.commit;
2913
+ handlersFinished.push(handler());
2864
2914
  }
2865
- // TODO: handle focus reset and scroll?
2915
+ // override old options with new ones. UA _may_ report a console warning if new options differ from previous
2916
+ event.commitOption = options?.commit ?? event.commitOption;
2917
+ event.scrollBehavior = options?.scroll ?? event.scrollBehavior;
2918
+ event.focusResetBehavior = options?.focusReset ?? event.focusResetBehavior;
2866
2919
  };
2867
2920
  event.scroll = function () {
2868
- // TODO: handle scroll?
2921
+ if (event.interceptionState !== 'committed') {
2922
+ throw new DOMException(`Failed to execute 'scroll' on 'NavigateEvent': scroll() must be ` +
2923
+ `called after commit() and interception options must specify manual scroll.`, 'InvalidStateError');
2924
+ }
2925
+ processScrollBehavior(event);
2869
2926
  };
2870
2927
  event.commit = function (internal = false) {
2871
- if (!internal && !interceptCalled) {
2928
+ if (!internal && this.interceptionState !== 'intercepted') {
2872
2929
  throw new DOMException(`Failed to execute 'commit' on 'NavigateEvent': intercept() must be ` +
2873
- `called before commit().`, 'InvalidStateError');
2930
+ `called before commit() and commit() cannot be already called.`, 'InvalidStateError');
2931
+ }
2932
+ if (!internal && event.commitOption !== 'after-transition') {
2933
+ throw new DOMException(`Failed to execute 'commit' on 'NavigateEvent': commit() may not be ` +
2934
+ `called if commit behavior is not "after-transition",.`, 'InvalidStateError');
2874
2935
  }
2875
2936
  if (!dispatchedNavigateEvent) {
2876
2937
  throw new DOMException(`Failed to execute 'commit' on 'NavigateEvent': commit() may not be ` +
2877
2938
  `called during event dispatch.`, 'InvalidStateError');
2878
2939
  }
2879
- if (commitCalled) {
2880
- throw new DOMException(`Failed to execute 'commit' on 'NavigateEvent': commit() already ` + `called.`, 'InvalidStateError');
2881
- }
2882
- commitCalled = true;
2883
- userAgentCommit();
2940
+ this.interceptionState = 'committed';
2941
+ result.navigation.commitNavigateEvent(event);
2884
2942
  };
2885
2943
  // Internal only.
2886
2944
  event.cancel = function (reason) {
2887
2945
  result.committedReject(reason);
2888
2946
  result.finishedReject(reason);
2889
2947
  };
2890
- // Internal only.
2891
- event.dispatchedNavigateEvent = function () {
2948
+ function dispatch() {
2949
+ navigation.navigateEvent = event;
2950
+ navigation.eventTarget.dispatchEvent(event);
2892
2951
  dispatchedNavigateEvent = true;
2893
- if (event.commitOption === 'after-transition') {
2894
- // If handler finishes before commit, call commit.
2895
- handlerFinished?.then(() => {
2896
- if (!commitCalled) {
2897
- event.commit(/* internal */ true);
2898
- }
2899
- }, () => { });
2952
+ if (event.interceptionState !== 'none') {
2953
+ navigation.transition = new InternalNavigationTransition(navigation.currentEntry, navigationType);
2954
+ if (event.commitOption !== 'after-transition') {
2955
+ event.commit(/** internal */ true);
2956
+ }
2900
2957
  }
2901
- Promise.all([result.committed, handlerFinished]).then(([entry]) => {
2902
- result.finishedResolve(entry);
2958
+ else {
2959
+ // In the spec, this isn't really part of the navigate API. Instead, the navigate event firing returns "true" to indicate
2960
+ // navigation steps should "continue" (https://whatpr.org/html/10919/browsing-the-web.html#beginning-navigation)
2961
+ event.commit(/** internal */ true);
2962
+ }
2963
+ Promise.all(handlersFinished).then(() => {
2964
+ // Follows steps outlined under "Wait for all of promisesList, with the following success steps:"
2965
+ // in the spec https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigate-event-firing.
2966
+ if (result.signal.aborted) {
2967
+ return;
2968
+ }
2969
+ if (event !== navigation.navigateEvent) {
2970
+ throw new Error("Navigation's ongoing event not equal to resolved event");
2971
+ }
2972
+ navigation.navigateEvent = null;
2973
+ if (event.interceptionState === 'intercepted') {
2974
+ navigation.commitNavigateEvent(event);
2975
+ }
2976
+ finishNavigationEvent(event, true);
2977
+ const navigatesuccessEvent = new Event('navigatesuccess', { bubbles: false, cancelable });
2978
+ navigation.eventTarget.dispatchEvent(navigatesuccessEvent);
2979
+ result.finishedResolve();
2980
+ if (navigation.transition !== null) {
2981
+ navigation.transition.finishedResolve();
2982
+ }
2983
+ navigation.transition = null;
2903
2984
  }, (reason) => {
2985
+ if (result.signal.aborted) {
2986
+ return;
2987
+ }
2988
+ if (event !== navigation.navigateEvent) {
2989
+ throw new Error("Navigation's ongoing event not equal to resolved event");
2990
+ }
2991
+ navigation.navigateEvent = null;
2992
+ event.interceptionState = 'rejected'; // TODO(atscott): this is not in the spec https://github.com/whatwg/html/issues/11087
2993
+ finishNavigationEvent(event, false);
2994
+ const navigateerrorEvent = new Event('navigateerror', { bubbles: false, cancelable });
2995
+ navigation.eventTarget.dispatchEvent(navigateerrorEvent);
2904
2996
  result.finishedReject(reason);
2997
+ if (navigation.transition !== null) {
2998
+ navigation.transition.finishedResolve();
2999
+ }
3000
+ navigation.transition = null;
2905
3001
  });
2906
- };
2907
- // Internal only.
2908
- event.userAgentNavigated = function (entry) {
2909
- result.committedResolve(entry);
2910
- };
3002
+ }
3003
+ dispatch();
2911
3004
  return event;
2912
3005
  }
3006
+ /** https://whatpr.org/html/10919/nav-history-apis.html#navigateevent-finish */
3007
+ function finishNavigationEvent(event, didFulfill) {
3008
+ if (event.interceptionState === 'intercepted' || event.interceptionState === 'finished') {
3009
+ throw new Error('Attempting to finish navigation event that was incomplete or already finished');
3010
+ }
3011
+ if (event.interceptionState === 'none') {
3012
+ return;
3013
+ }
3014
+ if (didFulfill) {
3015
+ // TODO(atscott): https://github.com/whatwg/html/issues/11087 focus reset is not guarded by didFulfill in the spec
3016
+ potentiallyResetFocus(event);
3017
+ potentiallyResetScroll(event);
3018
+ }
3019
+ event.interceptionState = 'finished';
3020
+ }
3021
+ /** https://whatpr.org/html/10919/nav-history-apis.html#potentially-reset-the-focus */
3022
+ function potentiallyResetFocus(event) {
3023
+ if (event.interceptionState !== 'committed' && event.interceptionState !== 'scrolled') {
3024
+ throw new Error('cannot reset focus if navigation event is not committed or scrolled');
3025
+ }
3026
+ // TODO(atscott): The rest of the steps
3027
+ }
3028
+ function potentiallyResetScroll(event) {
3029
+ if (event.interceptionState !== 'committed' && event.interceptionState !== 'scrolled') {
3030
+ throw new Error('cannot reset scroll if navigation event is not committed or scrolled');
3031
+ }
3032
+ if (event.interceptionState === 'scrolled' || event.scrollBehavior === 'manual') {
3033
+ return;
3034
+ }
3035
+ processScrollBehavior(event);
3036
+ }
3037
+ /* https://whatpr.org/html/10919/nav-history-apis.html#process-scroll-behavior */
3038
+ function processScrollBehavior(event) {
3039
+ if (event.interceptionState !== 'committed') {
3040
+ throw new Error('invalid event interception state when processing scroll behavior');
3041
+ }
3042
+ event.interceptionState = 'scrolled';
3043
+ // TODO(atscott): the rest of the steps
3044
+ }
2913
3045
  /**
2914
3046
  * Create a fake equivalent of `NavigationCurrentEntryChange`. This does not use
2915
3047
  * a class because ES5 transpiled JavaScript cannot extend native Event.
@@ -2969,8 +3101,28 @@ function isHashChange(from, to) {
2969
3101
  to.pathname === from.pathname &&
2970
3102
  to.search === from.search);
2971
3103
  }
2972
- /** Internal utility class for representing the result of a navigation. */
3104
+ class InternalNavigationTransition {
3105
+ from;
3106
+ navigationType;
3107
+ finished;
3108
+ finishedResolve;
3109
+ finishedReject;
3110
+ constructor(from, navigationType) {
3111
+ this.from = from;
3112
+ this.navigationType = navigationType;
3113
+ this.finished = new Promise((resolve, reject) => {
3114
+ this.finishedReject = reject;
3115
+ this.finishedResolve = resolve;
3116
+ });
3117
+ }
3118
+ }
3119
+ /**
3120
+ * Internal utility class for representing the result of a navigation.
3121
+ * Generally equivalent to the "apiMethodTracker" in the spec.
3122
+ */
2973
3123
  class InternalNavigationResult {
3124
+ navigation;
3125
+ committedTo = null;
2974
3126
  committedResolve;
2975
3127
  committedReject;
2976
3128
  finishedResolve;
@@ -2981,13 +3133,22 @@ class InternalNavigationResult {
2981
3133
  return this.abortController.signal;
2982
3134
  }
2983
3135
  abortController = new AbortController();
2984
- constructor() {
3136
+ constructor(navigation) {
3137
+ this.navigation = navigation;
2985
3138
  this.committed = new Promise((resolve, reject) => {
2986
- this.committedResolve = resolve;
3139
+ this.committedResolve = (entry) => {
3140
+ this.committedTo = entry;
3141
+ resolve(entry);
3142
+ };
2987
3143
  this.committedReject = reject;
2988
3144
  });
2989
3145
  this.finished = new Promise(async (resolve, reject) => {
2990
- this.finishedResolve = resolve;
3146
+ this.finishedResolve = () => {
3147
+ if (this.committedTo === null) {
3148
+ throw new Error('NavigateEvent should have been committed before resolving finished promise.');
3149
+ }
3150
+ resolve(this.committedTo);
3151
+ };
2991
3152
  this.finishedReject = (reason) => {
2992
3153
  reject(reason);
2993
3154
  this.abortController.abort(reason);