From eeea35a06198e098c53d36c98023935073b200b7 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Tue, 14 Oct 2025 09:43:45 +0100 Subject: [PATCH 01/14] Add precommitHandler() and other info to Navigation API docs --- files/en-us/web/api/navigateevent/index.md | 2 +- .../web/api/navigateevent/intercept/index.md | 148 +++++++++++++++++- files/en-us/web/api/navigation_api/index.md | 6 +- 3 files changed, 149 insertions(+), 7 deletions(-) diff --git a/files/en-us/web/api/navigateevent/index.md b/files/en-us/web/api/navigateevent/index.md index 9d1f82a6fe07e62..66c6032404c6f9e 100644 --- a/files/en-us/web/api/navigateevent/index.md +++ b/files/en-us/web/api/navigateevent/index.md @@ -50,7 +50,7 @@ _Inherits properties from its parent, {{DOMxRef("Event")}}._ _Inherits methods from its parent, {{DOMxRef("Event")}}._ - {{domxref("NavigateEvent.intercept", "intercept()")}} {{Experimental_Inline}} - - : Intercepts this navigation, turning it into a same-document navigation to the {{domxref("NavigationDestination.url", "destination")}} URL. It can accept a handler function that defines what the navigation handling behavior should be, plus `focusReset` and `scroll` options to control behavior as desired. + - : Intercepts this navigation, turning it into a same-document navigation to the {{domxref("NavigationDestination.url", "destination")}} URL. It can accept handler functions that define what the navigation handling behavior should be, plus `focusReset` and `scroll` options to enable or disable the browser's default focus and scrolling behavior as desired. - {{domxref("NavigateEvent.scroll", "scroll()")}} {{Experimental_Inline}} - : Can be called to manually trigger the browser-driven scrolling behavior that occurs in response to the navigation, if you want it to happen before the navigation handling has completed. diff --git a/files/en-us/web/api/navigateevent/intercept/index.md b/files/en-us/web/api/navigateevent/intercept/index.md index 03940df73bb99c1..98363c6ed0a6c60 100644 --- a/files/en-us/web/api/navigateevent/intercept/index.md +++ b/files/en-us/web/api/navigateevent/intercept/index.md @@ -25,7 +25,9 @@ intercept(options) - `options` {{optional_inline}} - : An options object containing the following properties: - `handler` {{optional_inline}} - - : A callback function that defines what the navigation handling behavior should be. This generally handles resource fetching, and returns a promise. + - : A callback function that defines what the navigation handling behavior should be; it returns a promise. This function will run after the {{domxref("Navigation.navigate_event", "navigate")}} event has fired and the {{domxref("Navigation.currentEntry", "currentEntry")}} property has been updated. + - `precommitHandler` {{optional_inline}} + - : A callback function that defines any behavior that should occur just before the navigation has finished; it accepts a controller object as an argument and returns a promise. This function will run after the {{domxref("Navigation.navigate_event", "navigate")}} event has fired but before the {{domxref("Navigation.currentEntry", "currentEntry")}} property has been updated. - `focusReset` {{optional_inline}} - : Defines the navigation's focus behavior. This may take one of the following values: - `after-transition` @@ -48,7 +50,145 @@ None (`undefined`). - `InvalidStateError` {{domxref("DOMException")}} - : Thrown if the current {{domxref("Document")}} is not yet active, or if the navigation has been cancelled. - `SecurityError` {{domxref("DOMException")}} - - : Thrown if the event was dispatched by a {{domxref("EventTarget.dispatchEvent", "dispatchEvent()")}} call, rather than the user agent, or if the navigation cannot be intercepted (i.e., {{domxref("NavigateEvent.canIntercept")}} is `false`). + - : Thrown if: + - The event was dispatched by a {{domxref("EventTarget.dispatchEvent", "dispatchEvent()")}} call, rather than the user agent. + - The navigation cannot be intercepted ({{domxref("NavigateEvent.canIntercept")}} is `false`). + - A `precommitHandler()` callback was called on a non-cancelable event ({{domxref("Event.cancelable")}} is `false`). + +## Description + +The `intercept()` method is used to implement custom navigation behavior when a link is clicked in an application. It does this via a couple of different callbacks. + +### Handling immediate navigations with `handler()` + +The `handler()` callback is generally used to handle resource fetching and updating the UI to show the content for the new page, which will typically look like this, at a basic level: + +```js +navigation.addEventListener("navigate", (event) => { + const url = new URL(event.destination.url); + + if (url.pathname.startsWith("/articles/")) { + event.intercept({ + async handler() { + // Fetch the new content and display when ready + const articleContent = await getArticleContent(url.pathname); + renderArticlePage(articleContent); + }, + }); + } + + // Include multiple conditions for different page types here, as needed +}); +``` + +`handler()` will run after the {{domxref("Navigation.navigate_event", "navigate")}} event has fired and the {{domxref("Navigation.currentEntry", "currentEntry")}} property has been updated, meaning that it should be used to implemnent navigation behavior where the navigation is committed to. The new URL is shown in the browser, and history is updated, and the user should be shown something new. + +### Handling precommit actions with `precommitHandler()` + +However, depending on the URL, you might not always want to commit the navigation immediately. For example, if the user navigates to a restricted page and the user is not signed in, you may want to redirect the browser to a sign-in page. This kind of scenario can be dealt with using the `precommitHandler()` callback, which will run after the {{domxref("Navigation.navigate_event", "navigate")}} event has fired but before the {{domxref("Navigation.currentEntry", "currentEntry")}} property has been updated. + +For example: + +```js +navigation.addEventListener("navigate", (event) => { + const url = new URL(event.destination.url); + + if (url.pathname.startsWith("/restricted/") && !userSignedIn) { + event.intercept({ + async precommitHandler(controller) { + controller.redirect("/signin/", { + state: "signin-redirect", + history: "push", + }); + }, + }); + } +}); +``` + +This pattern is simpler than the alternative of canceling the original navigation and starting a new one to the redirect location, because it avoids exposing the intermediate state. For example, only one `navigatesuccess` or `navigateerror` event fires, and if the navigation was triggered by a call to {{domxref("Navigation.navigate()")}}, the promise only fulfills once the redirect destination is reached. + +The `precommitHandler()` callback takes a `controller` object as an argument, which contains a `redirect()` method. The `redirect()` method takes two parameters — a string representing the URL to redirect to, and an options object containing two parameters: + +- `state` {{optional_inline}} + - : Contains any state information you want to pass along with the navigation, for example for logging or tracking purposes. +- `history` {{optional_inline}} + - : An enumerated value that specifies how this redirect should be added to the navigation history. It can take one of the following values: + - `auto` + - : The default value, which lets the browser decide how to handle it. Usually the value used is `push`, but it will become `replace` if the redirect points to the same URL as the pre-navigation URL. + - `push` + - : Adds a new {{domxref("NavigationHistoryEntry")}} to the navigation history, and clears any available forward navigation (that is, if the user previously navigated to other locations, then used the back button to return back through the history before then initiating the navigation that caused the redirect). + - `replace` + - : Replaces the {{domxref("Navigation.currentEntry")}} with the `NavigationHistoryEntry`. + +`precommitHandler()` generally handles any modifications to the navigation behavior that are required before the navigation actually occurs as a result of the destination URL, cancelling or redirecting it somewhere else as required. Because `precommitHandler()` can be used to cancel navigations, they can only be run when the event's {{domxref("Event.cancelable")}} property is `true`. Calling `intercept()` with a `precommitHandler()` on a non-cancelable event results in a `SecurityError` being thrown. + +### Responding to navigation success or failure + +When the promises returned by the `intercept()` handler functions fulfill, the `Navigation` object's {{domxref("Navigation/navigatesuccess_event", "navigatesuccess")}} event fires, allowing you to run cleanup code after a successful navigation has completed. If they reject, meaning the navigation has failed, {{domxref("Navigation/navigateerror_event", "navigateerror")}} fires instead, allowing you to gracefully handle the failure case. + +There is also a `finished` property on the return value of navigation methods (such as {{domxref("Navigation.navigate()")}}), which fulfills or rejects at the same time as the aforementioned events are fired, providing another path for handling the success and failure cases. + +### Interaction between `precommitHandler()` and `handler()` + +Both `precommitHandler()` and `handler()` callbacks can be included inside the same `intercept()` call. + +- When the `precommitHandler()` promise fulfills, the browser moves on to run any `handler()` callback present. If `precommitHandler()` rejects, `navigateerror` fires, the `committed` and `finished` promises reject, and the navigation is cancelled. +- Once the `precommitHandler()` promise fulfills and the navigation is committed, and a new {{domxref("NavigationHistoryEntry")}} is created for the navigation, its `committed` promise fulfills. +- When the `handler()` promise fulfills and the `navigatesuccess` event fires, the navigation `finished` promise fulfills as well, to indicate the navigation is finished. If `handler()` rejects, `navigateerror` fires, the `finished` promise rejects, and the navigation is cancelled. + +### Controlling focus behavior + +By default, after a navigation handled using `intercept()` has occurred, the document focus will reset to the first element in the DOM with an [`autofocus`](/en-US/docs/Web/HTML/Reference/Global_attributes/autofocus) attribute set, or the {{htmlelement("body")}} element, if no `autofocus` attribute is set. If you want to override this behavior, to manually implement a more accessible focus position on navigation (for example, the new top-level heading), you can do so by setting the `focusReset` option to `manual`. + +```js +navigation.addEventListener("navigate", (event) => { + const url = new URL(event.destination.url); + + if (url.pathname.startsWith("/articles/")) { + event.intercept({ + focusReset: manual, + async handler() { + // Fetch the new content and display when ready + const articleContent = await getArticleContent(url.pathname); + renderArticlePage(articleContent); + // Handle page focus with a custom function + setPageFocus(); + }, + }); + } +}); +``` + +### Controlling scroll behavior + +After an `intercept()` navigation occurs, the following scrolling behavior occurs: + +- For `push` and `replace` navigations (see {{domxref("Navigation.navigate()")}}), the browser will attempt to scroll to the fragment given by `event.destination.url`. If there is no fragment available, it will reset the scroll position to the top of the page. +- For {{domxref("Navigation.traverseTo", "traverse")}} and {{domxref("Navigation.reload", "reload")}} navigations, the browser behaves similarly to the previous bullet description, but delays its scroll restoration logic until the `intercept()` promise fulfills. It will perform no scroll restoration if the promise rejects. If the user has scrolled during the transition then no scroll restoration will be performed. + +If you want to turn this behavior off, you can do so by setting the `scroll` option to `manual`. + +```js +navigation.addEventListener("navigate", (event) => { + const url = new URL(event.destination.url); + + if (url.pathname.startsWith("/articles/")) { + event.intercept({ + scroll: manual, + async handler() { + // Fetch the new content and display when ready + const articleContent = await getArticleContent(url.pathname); + renderArticlePage(articleContent); + // Handle scroll behavior with a custom function + setScroll(); + }, + }); + } +}); +``` + +If you want to manually trigger the default scrolling behavior described earlier (maybe you want to reset the scroll position to the top of the page early, before the full navigation has finished), you can do so by calling {{domxref("NavigateEvent.scroll()")}}. ## Examples @@ -100,8 +240,8 @@ navigation.addEventListener("navigate", (event) => { method: "POST", body: event.formData, }); - // You could navigate again with {history: 'replace'} to change the URL here, - // which might indicate "done" + // You could navigate again with {history: 'replace'} to + // change the URL here, which might indicate "done" }, }); } diff --git a/files/en-us/web/api/navigation_api/index.md b/files/en-us/web/api/navigation_api/index.md index da8beda968d77e3..f3f7e99103e3c64 100644 --- a/files/en-us/web/api/navigation_api/index.md +++ b/files/en-us/web/api/navigation_api/index.md @@ -28,7 +28,9 @@ The `navigation` interface has several associated events, the most notable being The `NavigationEvent` object also provides two methods: -- {{domxref("NavigateEvent.intercept", "intercept()")}} takes as an argument a callback handler function returning a promise. It allows you to control what happens when the navigation is initiated. For example, in the case of an SPA, it can be used to load relevant new content into the UI based on the path of the URL navigated to. +- {{domxref("NavigateEvent.intercept", "intercept()")}} allows you to specify custom behavior for navigations, and can take the following as arguments: + - Callback handler functions allowing you to specify what happens both _when_ the navigation is initiated and _just before_ the navigation is initiated. For example, it could be used to load relevant new content into the UI based on the path of the URL navigated to, or redirect the browser to a sign-in page if the URL points to a restricted page and the user is not signed in. + - Properties that allow you to enable or disable the browser's default focus and scrolling behavior after the navigation occurs. - {{domxref("NavigateEvent.scroll", "scroll()")}} allows you to manually initiate the browser's scroll behavior (e.g., to a fragment identifier in the URL), if it makes sense for your code, rather than waiting for the browser to handle it automatically. Once a navigation is initiated, and your `intercept()` handler is called, a {{domxref("NavigationTransition")}} object instance is created (accessible via {{domxref("Navigation.transition")}}), which can be used to track the process of the ongoing navigation. @@ -39,7 +41,7 @@ Once a navigation is initiated, and your `intercept()` handler is called, a {{do > [!NOTE] > You can also call {{domxref("Event.preventDefault", "preventDefault()")}} to stop the navigation entirely for most [navigation types](/en-US/docs/Web/API/NavigateEvent/navigationType#value); cancellation of traverse navigations is not yet implemented. -When the `intercept()` handler function's promise fulfills, the `Navigation` object's {{domxref("Navigation/navigatesuccess_event", "navigatesuccess")}} event fires, allowing you to run cleanup code after a successful navigation has completed. If it rejects, meaning the navigation has failed, {{domxref("Navigation/navigateerror_event", "navigateerror")}} fires instead, allowing you to gracefully handle the failure case. There is also a {{domxref("NavigationTransition.finished", "finished")}} property on the `NavigationTransition` object, which fulfills or rejects at the same time as the aforementioned events are fired, providing another path for handling the success and failure cases. +When the promises returned by the `intercept()` handler functions fulfill, the `Navigation` object's {{domxref("Navigation/navigatesuccess_event", "navigatesuccess")}} event fires, allowing you to run cleanup code after a successful navigation has completed. If they reject, meaning the navigation has failed, {{domxref("Navigation/navigateerror_event", "navigateerror")}} fires instead, allowing you to gracefully handle the failure case. There is also a `finished` property on the return value of navigation methods (such as {{domxref("Navigation.navigate()")}}), which fulfills or rejects at the same time as the aforementioned events are fired, providing another path for handling the success and failure cases. > [!NOTE] > Before the Navigation API was available, to do something similar you'd have to listen for all click events on links, run `e.preventDefault()`, perform the appropriate {{domxref("History.pushState()")}} call, then set up the page view based on the new URL. And this wouldn't handle all navigations — only user-initiated link clicks. From 302dcd52ea7ef6e2602f7c474ab1d33da96af78a Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Tue, 14 Oct 2025 16:37:23 +0100 Subject: [PATCH 02/14] Fixes for noamr review comments --- .../web/api/navigateevent/intercept/index.md | 37 ++++++++++++------- files/en-us/web/api/navigation_api/index.md | 4 +- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/files/en-us/web/api/navigateevent/intercept/index.md b/files/en-us/web/api/navigateevent/intercept/index.md index 98363c6ed0a6c60..c7c55296376c7ea 100644 --- a/files/en-us/web/api/navigateevent/intercept/index.md +++ b/files/en-us/web/api/navigateevent/intercept/index.md @@ -25,9 +25,9 @@ intercept(options) - `options` {{optional_inline}} - : An options object containing the following properties: - `handler` {{optional_inline}} - - : A callback function that defines what the navigation handling behavior should be; it returns a promise. This function will run after the {{domxref("Navigation.navigate_event", "navigate")}} event has fired and the {{domxref("Navigation.currentEntry", "currentEntry")}} property has been updated. + - : A callback function that defines what the navigation handling behavior should be; it returns a promise. This function will run after the {{domxref("Navigation.currentEntry", "currentEntry")}} property has been updated. - `precommitHandler` {{optional_inline}} - - : A callback function that defines any behavior that should occur just before the navigation has finished; it accepts a controller object as an argument and returns a promise. This function will run after the {{domxref("Navigation.navigate_event", "navigate")}} event has fired but before the {{domxref("Navigation.currentEntry", "currentEntry")}} property has been updated. + - : A callback function that defines any behavior that should occur just before the navigation has finished; it accepts a controller object as an argument and returns a promise. This function will run before the {{domxref("Navigation.currentEntry", "currentEntry")}} property has been updated. - `focusReset` {{optional_inline}} - : Defines the navigation's focus behavior. This may take one of the following values: - `after-transition` @@ -53,15 +53,19 @@ None (`undefined`). - : Thrown if: - The event was dispatched by a {{domxref("EventTarget.dispatchEvent", "dispatchEvent()")}} call, rather than the user agent. - The navigation cannot be intercepted ({{domxref("NavigateEvent.canIntercept")}} is `false`). - - A `precommitHandler()` callback was called on a non-cancelable event ({{domxref("Event.cancelable")}} is `false`). + - A `precommitHandler()` callback was provided on a non-cancelable event ({{domxref("Event.cancelable")}} is `false`). ## Description -The `intercept()` method is used to implement custom navigation behavior when a link is clicked in an application. It does this via a couple of different callbacks. +The `intercept()` method is used to implement custom SPA navigation behavior when a navigation occurs, for example when a link is clicked, a form is submitted, or a programmatic navigation is initiated (using {{domxref("History.pushState()")}}, {{domxref("Window.location")}}, etc.). + +It does this via a couple of different callbacks, `handler()` and `precommitHandler()`. ### Handling immediate navigations with `handler()` -The `handler()` callback is generally used to handle resource fetching and updating the UI to show the content for the new page, which will typically look like this, at a basic level: +The `handler()` callback is run in response to a committed navigation. It will run after the {{domxref("Navigation.currentEntry", "currentEntry")}} property has been updated, meaning that a new URL is shown in the browser UI and the history is updated with a new entry. + +A typical example looks like this, enabling specific content to be rendered and loaded in response to a certain navigation: ```js navigation.addEventListener("navigate", (event) => { @@ -81,13 +85,13 @@ navigation.addEventListener("navigate", (event) => { }); ``` -`handler()` will run after the {{domxref("Navigation.navigate_event", "navigate")}} event has fired and the {{domxref("Navigation.currentEntry", "currentEntry")}} property has been updated, meaning that it should be used to implemnent navigation behavior where the navigation is committed to. The new URL is shown in the browser, and history is updated, and the user should be shown something new. +`handler()` should be used to implement navigation behavior where the navigation is committed to: the user should be shown something new. ### Handling precommit actions with `precommitHandler()` -However, depending on the URL, you might not always want to commit the navigation immediately. For example, if the user navigates to a restricted page and the user is not signed in, you may want to redirect the browser to a sign-in page. This kind of scenario can be dealt with using the `precommitHandler()` callback, which will run after the {{domxref("Navigation.navigate_event", "navigate")}} event has fired but before the {{domxref("Navigation.currentEntry", "currentEntry")}} property has been updated. +However, you might also wish to modify or cancel in-flight navigation, or to perform work while the navigation is ongoing and before it is committed. This kind of scenario can be dealt with using the `precommitHandler()` callback, which runs before the {{domxref("Navigation.currentEntry", "currentEntry")}} property has been updated and the browser UI shows the new location. -For example: +For example, if the user navigates to a restricted page and is not signed in, you may want to redirect the browser to a sign-in page. This might be handled like so: ```js navigation.addEventListener("navigate", (event) => { @@ -111,7 +115,7 @@ This pattern is simpler than the alternative of canceling the original navigatio The `precommitHandler()` callback takes a `controller` object as an argument, which contains a `redirect()` method. The `redirect()` method takes two parameters — a string representing the URL to redirect to, and an options object containing two parameters: - `state` {{optional_inline}} - - : Contains any state information you want to pass along with the navigation, for example for logging or tracking purposes. + - : Contains any state information you want to pass along with the navigation, for example, for logging or tracking purposes. The state for the navigation can subsequently be retrieved via {{domxref("NavigationHistoryEntry.getState()")}}. - `history` {{optional_inline}} - : An enumerated value that specifies how this redirect should be added to the navigation history. It can take one of the following values: - `auto` @@ -121,7 +125,7 @@ The `precommitHandler()` callback takes a `controller` object as an argument, wh - `replace` - : Replaces the {{domxref("Navigation.currentEntry")}} with the `NavigationHistoryEntry`. -`precommitHandler()` generally handles any modifications to the navigation behavior that are required before the navigation actually occurs as a result of the destination URL, cancelling or redirecting it somewhere else as required. Because `precommitHandler()` can be used to cancel navigations, they can only be run when the event's {{domxref("Event.cancelable")}} property is `true`. Calling `intercept()` with a `precommitHandler()` on a non-cancelable event results in a `SecurityError` being thrown. +`precommitHandler()` generally handles any modifications to the navigation behavior that are required before the destination URL is actually displayed in the browser, cancelling or redirecting it somewhere else as required. Because `precommitHandler()` can be used to cancel navigations, they can only be run when the event's {{domxref("Event.cancelable")}} property is `true`. Calling `intercept()` with a `precommitHandler()` on a non-cancelable event results in a `SecurityError` being thrown. ### Responding to navigation success or failure @@ -133,9 +137,16 @@ There is also a `finished` property on the return value of navigation methods (s Both `precommitHandler()` and `handler()` callbacks can be included inside the same `intercept()` call. -- When the `precommitHandler()` promise fulfills, the browser moves on to run any `handler()` callback present. If `precommitHandler()` rejects, `navigateerror` fires, the `committed` and `finished` promises reject, and the navigation is cancelled. -- Once the `precommitHandler()` promise fulfills and the navigation is committed, and a new {{domxref("NavigationHistoryEntry")}} is created for the navigation, its `committed` promise fulfills. -- When the `handler()` promise fulfills and the `navigatesuccess` event fires, the navigation `finished` promise fulfills as well, to indicate the navigation is finished. If `handler()` rejects, `navigateerror` fires, the `finished` promise rejects, and the navigation is cancelled. +1. First, the `precommitHandler()` handler runs. + - When the `precommitHandler()` promise fulfills, the navigation commits. + - If the `precommitHandler()` rejects, `navigateerror` fires, the `committed` and `finished` promises reject, and the navigation is cancelled. + +2. When the navigation commits, a new {{domxref("NavigationHistoryEntry")}} is created for the navigation, and its `committed` promise fulfills. +3. Next, the `handler()` promise runs. + - When the `handler()` promise fulfills and the `navigatesuccess` event fires, the navigation `finished` promise fulfills as well, to indicate the navigation is finished. + - If `handler()` rejects, `navigateerror` fires, the `finished` promise rejects, and the navigation is cancelled. + +Note that the above process is upheld even across multiple `intercept()` called on the same `NavigateEvent`. All `precommitHandler()` callbacks are called first, and when all of them resolve, the navigation commits and all the `handler()` callbacks are called. ### Controlling focus behavior diff --git a/files/en-us/web/api/navigation_api/index.md b/files/en-us/web/api/navigation_api/index.md index f3f7e99103e3c64..f07cc2d03328565 100644 --- a/files/en-us/web/api/navigation_api/index.md +++ b/files/en-us/web/api/navigation_api/index.md @@ -29,7 +29,7 @@ The `navigation` interface has several associated events, the most notable being The `NavigationEvent` object also provides two methods: - {{domxref("NavigateEvent.intercept", "intercept()")}} allows you to specify custom behavior for navigations, and can take the following as arguments: - - Callback handler functions allowing you to specify what happens both _when_ the navigation is initiated and _just before_ the navigation is initiated. For example, it could be used to load relevant new content into the UI based on the path of the URL navigated to, or redirect the browser to a sign-in page if the URL points to a restricted page and the user is not signed in. + - Callback handler functions allowing you to specify what happens both _when_ the navigation is committed and _just before_ the navigation is committed. For example, it could be used to load relevant new content into the UI based on the path of the URL navigated to, or redirect the browser to a sign-in page if the URL points to a restricted page and the user is not signed in. - Properties that allow you to enable or disable the browser's default focus and scrolling behavior after the navigation occurs. - {{domxref("NavigateEvent.scroll", "scroll()")}} allows you to manually initiate the browser's scroll behavior (e.g., to a fragment identifier in the URL), if it makes sense for your code, rather than waiting for the browser to handle it automatically. @@ -41,7 +41,7 @@ Once a navigation is initiated, and your `intercept()` handler is called, a {{do > [!NOTE] > You can also call {{domxref("Event.preventDefault", "preventDefault()")}} to stop the navigation entirely for most [navigation types](/en-US/docs/Web/API/NavigateEvent/navigationType#value); cancellation of traverse navigations is not yet implemented. -When the promises returned by the `intercept()` handler functions fulfill, the `Navigation` object's {{domxref("Navigation/navigatesuccess_event", "navigatesuccess")}} event fires, allowing you to run cleanup code after a successful navigation has completed. If they reject, meaning the navigation has failed, {{domxref("Navigation/navigateerror_event", "navigateerror")}} fires instead, allowing you to gracefully handle the failure case. There is also a `finished` property on the return value of navigation methods (such as {{domxref("Navigation.navigate()")}}), which fulfills or rejects at the same time as the aforementioned events are fired, providing another path for handling the success and failure cases. +When the promises returned by the `intercept()` handler functions fulfill, the `Navigation` object's {{domxref("Navigation/navigatesuccess_event", "navigatesuccess")}} event fires, allowing you to run cleanup code after a successful navigation has completed. If they reject, meaning the navigation has failed, {{domxref("Navigation/navigateerror_event", "navigateerror")}} fires instead, allowing you to gracefully handle failure case. There is also a `finished` property on the return value of navigation methods (such as {{domxref("Navigation.navigate()")}}), which fulfills or rejects at the same time as the aforementioned events are fired, providing another path for handling the success and failure cases. > [!NOTE] > Before the Navigation API was available, to do something similar you'd have to listen for all click events on links, run `e.preventDefault()`, perform the appropriate {{domxref("History.pushState()")}} call, then set up the page view based on the new URL. And this wouldn't handle all navigations — only user-initiated link clicks. From e96042f1c45407e3a9e0bb5d1011322375150ac3 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Tue, 14 Oct 2025 16:39:08 +0100 Subject: [PATCH 03/14] Fixes for noamr review comments --- files/en-us/web/api/navigateevent/intercept/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/files/en-us/web/api/navigateevent/intercept/index.md b/files/en-us/web/api/navigateevent/intercept/index.md index c7c55296376c7ea..0f794abc34f1496 100644 --- a/files/en-us/web/api/navigateevent/intercept/index.md +++ b/files/en-us/web/api/navigateevent/intercept/index.md @@ -144,9 +144,9 @@ Both `precommitHandler()` and `handler()` callbacks can be included inside the s 2. When the navigation commits, a new {{domxref("NavigationHistoryEntry")}} is created for the navigation, and its `committed` promise fulfills. 3. Next, the `handler()` promise runs. - When the `handler()` promise fulfills and the `navigatesuccess` event fires, the navigation `finished` promise fulfills as well, to indicate the navigation is finished. - - If `handler()` rejects, `navigateerror` fires, the `finished` promise rejects, and the navigation is cancelled. + - If `handler()` rejects, `navigateerror` fires, the `finished` promise rejects, and the navigation is canceled. -Note that the above process is upheld even across multiple `intercept()` called on the same `NavigateEvent`. All `precommitHandler()` callbacks are called first, and when all of them resolve, the navigation commits and all the `handler()` callbacks are called. +Note that the above process is upheld even across multiple `intercept()` calls on the same `NavigateEvent`. All `precommitHandler()` callbacks are called first, and when all of them resolve, the navigation commits, and all the `handler()` callbacks are called. ### Controlling focus behavior From 2b0b059a6f855b2dc6d96624cedc412f1b401634 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Mon, 20 Oct 2025 10:36:57 +0100 Subject: [PATCH 04/14] Update files/en-us/web/api/navigateevent/intercept/index.md Co-authored-by: Noam Rosenthal --- files/en-us/web/api/navigateevent/intercept/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/en-us/web/api/navigateevent/intercept/index.md b/files/en-us/web/api/navigateevent/intercept/index.md index 0f794abc34f1496..fa55d89188bd3a2 100644 --- a/files/en-us/web/api/navigateevent/intercept/index.md +++ b/files/en-us/web/api/navigateevent/intercept/index.md @@ -27,7 +27,7 @@ intercept(options) - `handler` {{optional_inline}} - : A callback function that defines what the navigation handling behavior should be; it returns a promise. This function will run after the {{domxref("Navigation.currentEntry", "currentEntry")}} property has been updated. - `precommitHandler` {{optional_inline}} - - : A callback function that defines any behavior that should occur just before the navigation has finished; it accepts a controller object as an argument and returns a promise. This function will run before the {{domxref("Navigation.currentEntry", "currentEntry")}} property has been updated. + - : A callback function that defines any behavior that should occur just before the navigation has committed; it accepts a controller object as an argument and returns a promise. This function will run before the {{domxref("Navigation.currentEntry", "currentEntry")}} property has been updated. - `focusReset` {{optional_inline}} - : Defines the navigation's focus behavior. This may take one of the following values: - `after-transition` From 3aa7ca342289f7c97ebaa1fd7666f156cd269171 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Mon, 20 Oct 2025 10:50:33 +0100 Subject: [PATCH 05/14] Fixes for noamr review comments --- files/en-us/web/api/navigateevent/intercept/index.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/files/en-us/web/api/navigateevent/intercept/index.md b/files/en-us/web/api/navigateevent/intercept/index.md index fa55d89188bd3a2..f2670ca40c085aa 100644 --- a/files/en-us/web/api/navigateevent/intercept/index.md +++ b/files/en-us/web/api/navigateevent/intercept/index.md @@ -57,7 +57,7 @@ None (`undefined`). ## Description -The `intercept()` method is used to implement custom SPA navigation behavior when a navigation occurs, for example when a link is clicked, a form is submitted, or a programmatic navigation is initiated (using {{domxref("History.pushState()")}}, {{domxref("Window.location")}}, etc.). +The `intercept()` method is used to implement same-document (SPA) navigation behavior when a navigation occurs, for example when a link is clicked, a form is submitted, or a programmatic navigation is initiated (using {{domxref("History.pushState()")}}, {{domxref("Window.location")}}, etc.). It does this via a couple of different callbacks, `handler()` and `precommitHandler()`. @@ -119,12 +119,17 @@ The `precommitHandler()` callback takes a `controller` object as an argument, wh - `history` {{optional_inline}} - : An enumerated value that specifies how this redirect should be added to the navigation history. It can take one of the following values: - `auto` - - : The default value, which lets the browser decide how to handle it. Usually the value used is `push`, but it will become `replace` if the redirect points to the same URL as the pre-navigation URL. + - : The default value, which lets the browser decide how to handle it: + - If the original navigation occurred as a result of a {{domxref("Navigation.navigate()")}} call, the value will be whatever was specified in the `navigate()` call's [`history`](/en-US/docs/Web/API/Navigation/navigate#history) option. + - Otherwise, the value used is usually `push`, but it will become `replace` if the redirect points to the same URL as the pre-navigation URL. - `push` - : Adds a new {{domxref("NavigationHistoryEntry")}} to the navigation history, and clears any available forward navigation (that is, if the user previously navigated to other locations, then used the back button to return back through the history before then initiating the navigation that caused the redirect). - `replace` - : Replaces the {{domxref("Navigation.currentEntry")}} with the `NavigationHistoryEntry`. +> [!NOTE] +> The `redirect()` method can can convert the history behavior between `auto`, `push`, and `replace`, but it cannot turn a `traverse` navigation into a `push`/`replace` navigation and vice versa. + `precommitHandler()` generally handles any modifications to the navigation behavior that are required before the destination URL is actually displayed in the browser, cancelling or redirecting it somewhere else as required. Because `precommitHandler()` can be used to cancel navigations, they can only be run when the event's {{domxref("Event.cancelable")}} property is `true`. Calling `intercept()` with a `precommitHandler()` on a non-cancelable event results in a `SecurityError` being thrown. ### Responding to navigation success or failure From 4899165c22b4ae3aea3af78df3f42cc68b0b98f6 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Thu, 23 Oct 2025 07:58:29 +0100 Subject: [PATCH 06/14] Update files/en-us/web/api/navigateevent/intercept/index.md Co-authored-by: sideshowbarker --- files/en-us/web/api/navigateevent/intercept/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/en-us/web/api/navigateevent/intercept/index.md b/files/en-us/web/api/navigateevent/intercept/index.md index f2670ca40c085aa..f9061eb804e9dc8 100644 --- a/files/en-us/web/api/navigateevent/intercept/index.md +++ b/files/en-us/web/api/navigateevent/intercept/index.md @@ -53,7 +53,7 @@ None (`undefined`). - : Thrown if: - The event was dispatched by a {{domxref("EventTarget.dispatchEvent", "dispatchEvent()")}} call, rather than the user agent. - The navigation cannot be intercepted ({{domxref("NavigateEvent.canIntercept")}} is `false`). - - A `precommitHandler()` callback was provided on a non-cancelable event ({{domxref("Event.cancelable")}} is `false`). + - A `precommitHandler()` callback is provided on a non-cancelable event ({{domxref("Event.cancelable")}} is `false`). ## Description From a55cc5d2b1908722f459d5605c8447748d6e96bb Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Thu, 23 Oct 2025 07:58:51 +0100 Subject: [PATCH 07/14] Update files/en-us/web/api/navigateevent/intercept/index.md Co-authored-by: sideshowbarker --- files/en-us/web/api/navigateevent/intercept/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/en-us/web/api/navigateevent/intercept/index.md b/files/en-us/web/api/navigateevent/intercept/index.md index f9061eb804e9dc8..fb8199601fb9d49 100644 --- a/files/en-us/web/api/navigateevent/intercept/index.md +++ b/files/en-us/web/api/navigateevent/intercept/index.md @@ -57,7 +57,7 @@ None (`undefined`). ## Description -The `intercept()` method is used to implement same-document (SPA) navigation behavior when a navigation occurs, for example when a link is clicked, a form is submitted, or a programmatic navigation is initiated (using {{domxref("History.pushState()")}}, {{domxref("Window.location")}}, etc.). +The `intercept()` method is used to implement same-document (SPA) navigation behavior when a navigation occurs; for example, when a link is clicked, a form is submitted, or a programmatic navigation is initiated (using {{domxref("History.pushState()")}}, {{domxref("Window.location")}}, etc.). It does this via a couple of different callbacks, `handler()` and `precommitHandler()`. From 4ce39656b26a697173f7f8623c2de78be98db3ce Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Thu, 23 Oct 2025 07:59:34 +0100 Subject: [PATCH 08/14] Update files/en-us/web/api/navigateevent/intercept/index.md Co-authored-by: sideshowbarker --- files/en-us/web/api/navigateevent/intercept/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/en-us/web/api/navigateevent/intercept/index.md b/files/en-us/web/api/navigateevent/intercept/index.md index fb8199601fb9d49..bfd1f31aeb4f116 100644 --- a/files/en-us/web/api/navigateevent/intercept/index.md +++ b/files/en-us/web/api/navigateevent/intercept/index.md @@ -115,7 +115,7 @@ This pattern is simpler than the alternative of canceling the original navigatio The `precommitHandler()` callback takes a `controller` object as an argument, which contains a `redirect()` method. The `redirect()` method takes two parameters — a string representing the URL to redirect to, and an options object containing two parameters: - `state` {{optional_inline}} - - : Contains any state information you want to pass along with the navigation, for example, for logging or tracking purposes. The state for the navigation can subsequently be retrieved via {{domxref("NavigationHistoryEntry.getState()")}}. + - : Contains any state information you want to pass along with the navigation; for example, for logging or tracking purposes. The state for the navigation can subsequently be retrieved via {{domxref("NavigationHistoryEntry.getState()")}}. - `history` {{optional_inline}} - : An enumerated value that specifies how this redirect should be added to the navigation history. It can take one of the following values: - `auto` From 939938459a90286801097607b71bf47752537c9f Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Thu, 23 Oct 2025 08:00:37 +0100 Subject: [PATCH 09/14] Update files/en-us/web/api/navigateevent/intercept/index.md Co-authored-by: sideshowbarker --- files/en-us/web/api/navigateevent/intercept/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/en-us/web/api/navigateevent/intercept/index.md b/files/en-us/web/api/navigateevent/intercept/index.md index bfd1f31aeb4f116..d03e776e2509b1a 100644 --- a/files/en-us/web/api/navigateevent/intercept/index.md +++ b/files/en-us/web/api/navigateevent/intercept/index.md @@ -130,7 +130,7 @@ The `precommitHandler()` callback takes a `controller` object as an argument, wh > [!NOTE] > The `redirect()` method can can convert the history behavior between `auto`, `push`, and `replace`, but it cannot turn a `traverse` navigation into a `push`/`replace` navigation and vice versa. -`precommitHandler()` generally handles any modifications to the navigation behavior that are required before the destination URL is actually displayed in the browser, cancelling or redirecting it somewhere else as required. Because `precommitHandler()` can be used to cancel navigations, they can only be run when the event's {{domxref("Event.cancelable")}} property is `true`. Calling `intercept()` with a `precommitHandler()` on a non-cancelable event results in a `SecurityError` being thrown. +`precommitHandler()` generally handles any modifications to the navigation behavior that are required before the destination URL is actually displayed in the browser, cancelling or redirecting it somewhere else as required. Because `precommitHandler()` can be used to cancel navigations, it will only work as expected when the event's {{domxref("Event.cancelable")}} property is `true`. Calling `intercept()` with a `precommitHandler()` on a non-cancelable event results in a `SecurityError` being thrown. ### Responding to navigation success or failure From f30eb62fb3161ff3d24177bc35824f3cd2d40773 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Thu, 23 Oct 2025 08:01:22 +0100 Subject: [PATCH 10/14] Update files/en-us/web/api/navigateevent/intercept/index.md Co-authored-by: sideshowbarker --- files/en-us/web/api/navigateevent/intercept/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/en-us/web/api/navigateevent/intercept/index.md b/files/en-us/web/api/navigateevent/intercept/index.md index d03e776e2509b1a..7b4de8febcefb19 100644 --- a/files/en-us/web/api/navigateevent/intercept/index.md +++ b/files/en-us/web/api/navigateevent/intercept/index.md @@ -134,7 +134,7 @@ The `precommitHandler()` callback takes a `controller` object as an argument, wh ### Responding to navigation success or failure -When the promises returned by the `intercept()` handler functions fulfill, the `Navigation` object's {{domxref("Navigation/navigatesuccess_event", "navigatesuccess")}} event fires, allowing you to run cleanup code after a successful navigation has completed. If they reject, meaning the navigation has failed, {{domxref("Navigation/navigateerror_event", "navigateerror")}} fires instead, allowing you to gracefully handle the failure case. +When the promises returned by the `intercept()` handler functions fulfill, the `Navigation` object's {{domxref("Navigation/navigatesuccess_event", "navigatesuccess")}} event fires, allowing you to run cleanup code after a successful navigation has completed. If those promises reject, meaning the navigation has failed, {{domxref("Navigation/navigateerror_event", "navigateerror")}} fires instead, allowing you to gracefully handle the failure case. There is also a `finished` property on the return value of navigation methods (such as {{domxref("Navigation.navigate()")}}), which fulfills or rejects at the same time as the aforementioned events are fired, providing another path for handling the success and failure cases. From 077fe3cd42c2fa1dc94c960806e344b54d656142 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Thu, 23 Oct 2025 08:02:03 +0100 Subject: [PATCH 11/14] Update files/en-us/web/api/navigateevent/intercept/index.md Co-authored-by: sideshowbarker --- files/en-us/web/api/navigateevent/intercept/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/files/en-us/web/api/navigateevent/intercept/index.md b/files/en-us/web/api/navigateevent/intercept/index.md index 7b4de8febcefb19..5da9cd7d2b5a416 100644 --- a/files/en-us/web/api/navigateevent/intercept/index.md +++ b/files/en-us/web/api/navigateevent/intercept/index.md @@ -147,6 +147,7 @@ Both `precommitHandler()` and `handler()` callbacks can be included inside the s - If the `precommitHandler()` rejects, `navigateerror` fires, the `committed` and `finished` promises reject, and the navigation is cancelled. 2. When the navigation commits, a new {{domxref("NavigationHistoryEntry")}} is created for the navigation, and its `committed` promise fulfills. + 3. Next, the `handler()` promise runs. - When the `handler()` promise fulfills and the `navigatesuccess` event fires, the navigation `finished` promise fulfills as well, to indicate the navigation is finished. - If `handler()` rejects, `navigateerror` fires, the `finished` promise rejects, and the navigation is canceled. From 1570bfc851753b86b31355abcac2ef592725de8d Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Thu, 23 Oct 2025 08:02:52 +0100 Subject: [PATCH 12/14] Update files/en-us/web/api/navigateevent/intercept/index.md Co-authored-by: sideshowbarker --- files/en-us/web/api/navigateevent/intercept/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/en-us/web/api/navigateevent/intercept/index.md b/files/en-us/web/api/navigateevent/intercept/index.md index 5da9cd7d2b5a416..195e33a8383c3f8 100644 --- a/files/en-us/web/api/navigateevent/intercept/index.md +++ b/files/en-us/web/api/navigateevent/intercept/index.md @@ -156,7 +156,7 @@ Note that the above process is upheld even across multiple `intercept()` calls o ### Controlling focus behavior -By default, after a navigation handled using `intercept()` has occurred, the document focus will reset to the first element in the DOM with an [`autofocus`](/en-US/docs/Web/HTML/Reference/Global_attributes/autofocus) attribute set, or the {{htmlelement("body")}} element, if no `autofocus` attribute is set. If you want to override this behavior, to manually implement a more accessible focus position on navigation (for example, the new top-level heading), you can do so by setting the `focusReset` option to `manual`. +By default, after a navigation handled using `intercept()` has occurred, the document focus will reset to the first element in the DOM with an [`autofocus`](/en-US/docs/Web/HTML/Reference/Global_attributes/autofocus) attribute set, or otherwise to the {{htmlelement("body")}} element, if no `autofocus` attribute is set. If you want to override this behavior, to manually implement a more accessible focus position on navigation (for example, the new top-level heading), you can do so by setting the `focusReset` option to `manual`. ```js navigation.addEventListener("navigate", (event) => { From 5cb3235c9ee6abf53a4294a5450824e5d6be2c9a Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Thu, 23 Oct 2025 08:05:33 +0100 Subject: [PATCH 13/14] Update files/en-us/web/api/navigateevent/intercept/index.md Co-authored-by: sideshowbarker --- files/en-us/web/api/navigateevent/intercept/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/en-us/web/api/navigateevent/intercept/index.md b/files/en-us/web/api/navigateevent/intercept/index.md index 195e33a8383c3f8..8aa76a939126a79 100644 --- a/files/en-us/web/api/navigateevent/intercept/index.md +++ b/files/en-us/web/api/navigateevent/intercept/index.md @@ -182,7 +182,7 @@ navigation.addEventListener("navigate", (event) => { After an `intercept()` navigation occurs, the following scrolling behavior occurs: - For `push` and `replace` navigations (see {{domxref("Navigation.navigate()")}}), the browser will attempt to scroll to the fragment given by `event.destination.url`. If there is no fragment available, it will reset the scroll position to the top of the page. -- For {{domxref("Navigation.traverseTo", "traverse")}} and {{domxref("Navigation.reload", "reload")}} navigations, the browser behaves similarly to the previous bullet description, but delays its scroll restoration logic until the `intercept()` promise fulfills. It will perform no scroll restoration if the promise rejects. If the user has scrolled during the transition then no scroll restoration will be performed. +- For {{domxref("Navigation.traverseTo", "traverse")}} and {{domxref("Navigation.reload", "reload")}} navigations, the browser behaves similarly to the description in the previous item above in this list, but delays its scroll restoration logic until the `intercept()` promise fulfills. It will perform no scroll restoration if the promise rejects. If the user has scrolled during the transition then no scroll restoration will be performed. If you want to turn this behavior off, you can do so by setting the `scroll` option to `manual`. From 74ab1a502edf5e247e34f9f5ae908da617c15b61 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Thu, 23 Oct 2025 08:06:25 +0100 Subject: [PATCH 14/14] Update files/en-us/web/api/navigation_api/index.md Co-authored-by: sideshowbarker --- files/en-us/web/api/navigation_api/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/en-us/web/api/navigation_api/index.md b/files/en-us/web/api/navigation_api/index.md index f07cc2d03328565..958e40f55ea8971 100644 --- a/files/en-us/web/api/navigation_api/index.md +++ b/files/en-us/web/api/navigation_api/index.md @@ -29,7 +29,7 @@ The `navigation` interface has several associated events, the most notable being The `NavigationEvent` object also provides two methods: - {{domxref("NavigateEvent.intercept", "intercept()")}} allows you to specify custom behavior for navigations, and can take the following as arguments: - - Callback handler functions allowing you to specify what happens both _when_ the navigation is committed and _just before_ the navigation is committed. For example, it could be used to load relevant new content into the UI based on the path of the URL navigated to, or redirect the browser to a sign-in page if the URL points to a restricted page and the user is not signed in. + - Callback handler functions allowing you to specify what happens both _when_ the navigation is committed and _just before_ the navigation is committed. For example, you could load relevant new content into the UI based on the path of the URL navigated to, or redirect the browser to a sign-in page if the URL points to a restricted page and the user is not signed in. - Properties that allow you to enable or disable the browser's default focus and scrolling behavior after the navigation occurs. - {{domxref("NavigateEvent.scroll", "scroll()")}} allows you to manually initiate the browser's scroll behavior (e.g., to a fragment identifier in the URL), if it makes sense for your code, rather than waiting for the browser to handle it automatically.