v151 · navigation · behavior change · shipped
Navigation: Ignore duplicate navigations
Prevents an ongoing navigation from being unnecessarily canceled by a new, identical navigation that is initiated in quick succession. This optimization improves performance and the user experience by not wasting resources on a duplicate request, which can be caused by accidental double-clicks.
at a glance
| What changes | A duplicate navigation — one that exactly matches an in-flight navigation on the same navigable and starts less than 3 seconds after it — is dropped instead of canceling and replacing the ongoing navigation |
|---|---|
| Where | The navigate algorithm in the HTML Standard (all navigation types: link clicks, location.href assignments, window.open(), navigation.navigate(), reloads) — history traversals use a different algorithm and are not affected |
| Window | 3 seconds between navigation start times (Chromium experimented with 1 s, 2 s, and 3 s and chose 3 s for the best latency reduction) |
| Web-observable effects | Mostly silent: the first navigation simply completes. Observable via WebDriver BiDi navigation failed (status "canceled") for automation, and via navigation timing showing the first attempt's start time |
| Chrome status | Enabled by default in Chrome 151 (the milestone listing is authoritative, verified 2026-07-28) |
| Chromium gate | kIgnoreDuplicateNavs — FEATURE_ENABLED_BY_DEFAULT, with feature params (threshold, initiator-type skips, origin allowlist) |
| ChromeStatus | 5137490012930048 — Navigation: Ignore duplicate navigations |
why it exists
Users sometimes navigate to the same URL in quick succession — typically an accidental double-click on a link, or clicking a link again because the page appears not to respond. Because a new navigation takes precedence over an older one, the second navigation cancels the in-flight one: any response already on the wire is wasted, and the user's wait restarts from zero even though the first request may have been nearly complete. Chromium experimented with this optimization alongside a partner and saw significant latency improvements; ignoring the duplicate lets the first navigation finish undisturbed.
Source: whatwg/html issue #11743 — proposal; ChromeStatus motivation.the deduplication decision (syntax / algorithm entry point)
The change inserts a deduplication step into the navigate algorithm, after the point where navigations that are downloads or that return a 404-style early exit are handled and before the new navigation is recorded as the navigable's ongoing navigation:
- Capture navigationStartTime at the top of navigate (the unsafe shared current time).
- If the navigable's ongoing navigation is non-null, compare the new navigation against the stored navigation initiation snapshot params (the match conditions below).
- If all match conditions hold, the new navigation is a duplicate: invoke WebDriver BiDi navigation failed for it with status
"canceled"and return early — the ongoing navigation is left untouched and proceeds to completion. - Otherwise, store a fresh snapshot from the new navigation's parameters, reset navigation conditions potentially changed to false, and continue the navigation as normal.
match conditions (inputs compared)
A new navigation is a duplicate of the ongoing navigation only when every one of these is true (comparison is against the snapshot captured when the ongoing navigation started):
| Condition | Meaning / consequence |
|---|---|
| URLs equal | The new navigation's URL equals the snapshot's URL |
| Initiator origins same origin | The new navigation's initiator origin snapshot is same origin with the snapshot's initiator origin (null initiators and cross-origin re-triggers never deduplicate) |
| Both document resources null | Neither navigation carries a document resource (excludes e.g. srcdoc-style supplied content) |
| Same history handling behavior | Identical NavigationHistoryBehavior ("push" vs "replace" never deduplicate) |
| Both form data entry lists null | Form submissions are never deduplicated (a form POST/GET carries an entry list) |
| Same referrer policy | Identical referrer policy |
| Same user navigation involvement | Identical user navigation involvement (e.g. browser-UI vs activation-behavior vs none) |
| Start-time delta < 3 s | The new navigation's start time minus the snapshot's start time is less than 3 seconds |
| Conditions unchanged | The navigable's navigation conditions potentially changed boolean is false (see the cookie carve-out) |
navigation initiation snapshot params (the stored state)
To make the comparison possible, the PR adds a navigation initiation snapshot params struct to each navigable, captured at the start of every non-duplicated navigation. Its items:
| Item | Type | Captured from |
|---|---|---|
| URL | a URL | the navigation's URL |
| initiator origin | an origin or null | the navigation's initiator origin snapshot |
| document resource | a string or null | the navigation's document resource |
| history handling behavior | a NavigationHistoryBehavior | the navigation's history handling |
| entry list | an entry list or null | the navigation's form data entry list |
| referrer policy | a referrer policy | the navigation's referrer policy |
| user navigation involvement | a user navigation involvement | the navigation's user involvement |
| navigation start time | a number | the unsafe shared current time at navigation start |
The snapshot is stored per navigable (top-level traversables and child navigables such as iframes deduplicate independently) and is overwritten on every navigation that is not itself dropped as a duplicate — so a third identical navigation within the window is still compared against the surviving first navigation's snapshot.
Source: whatwg/html PR #11765 diff — struct definition and snapshot-update step.the cookie carve-out: navigation conditions potentially changed
Comparing request headers — including cookies — between the two navigations was considered and rejected during design: headers are only assembled deep inside Fetch, and comparing cookie values could disclose whether an HttpOnly cookie changed between the two attempts. Instead, the PR adds a per-navigable boolean, navigation conditions potentially changed (initially false):
- Setting
document.cookiesets it to true (the PR's cookie-setter step does this explicitly; a note records that the Cookie Store API does the same). - While true, deduplication is disabled — a navigation that would otherwise match is allowed to proceed, because a server that renders from cookies could legitimately return different content.
- Starting any non-duplicate navigation resets it to false (the fresh snapshot marks the new baseline).
The residual risk the design accepts: state changes that are not visible to these setters (a cookie written by an unrelated fetch's Set-Cookie response, or a server-side change between the two attempts) do not block deduplication — the 3-second window bounds how stale the reused navigation can be.
what a developer can observe
| Page behavior | The duplicate navigation is a silent no-op: no second fetch, no second Document, no second navigate event. The surviving (first) navigation completes and its document loads |
|---|---|
| Navigation timing | The loaded document's Navigation Timing start time reflects the first attempt — user-perceived latency is measured from the initial click, not the accidental second one (called out in the proposal as the intended observable difference) |
| WebDriver BiDi | browsingContext.navigationFailed fires for the dropped navigation with status "canceled", so automation does not hang waiting for a navigation that was silently dropped |
navigation.navigate() promises | Open spec question. The proposal says an ignored duplicate made via the Navigation API should reject its promise, but PR #11765 as written contains no Navigation-API promise steps (the author noted a need to inform the Navigation API about an aborted navigation not yet owned by the navigable). Treat promise behavior for deduplicated navigate() calls as unspecified until the PR resolves it |
edge cases and exclusions (error / non-dedup paths)
- Form submissions never deduplicate — a submission carries a form data entry list, failing the both-null condition (both GET and POST forms proceed).
- History traversals are unaffected — back/forward and
navigation.traverseTo()run the traverse algorithm, not navigate. - Cross-origin or differently-initiated duplicates proceed — the initiator-origin and user-involvement conditions scope deduplication to genuinely identical triggers.
- After the window — a matching navigation 3 seconds or more later proceeds normally (and becomes the new snapshot baseline).
- After a cookie write — the conditions-changed boolean forces the next navigation through (see the carve-out).
- No page-visible error — dropping the duplicate is silent by design; the only error-path signal is the WebDriver BiDi
"canceled"status for automation.
context and exposure
This is a browser-engine behavior change with no new web-facing API surface: no interfaces, methods, properties, or flags for developers to call or feature-detect. It applies to every navigable (top-level and child) and every navigation type routed through navigate, independent of secure context, permissions policy, or document state, subject only to the match conditions. Chromium gates it with the kIgnoreDuplicateNavs base feature (enabled by default), which exposes Finch/feature params: duplicate_nav_threshold (default 3000 ms), skip_ignore_browser_initiated_navs (default false), skip_ignore_renderer_initiated_navs (default false), and an origin allowlist; a separate modifier kIgnoreDuplicateNavsOnlyWithUserGesture (restricting dedup to user-gesture initiations) exists but is disabled by default.
lifecycle and timing
Ordering within a navigable: (1) a navigation starts and records its snapshot; (2) for the next 3 seconds, identical new navigations are dropped; (3) the first navigation that is not dropped — different parameters, window expired, or conditions changed — overwrites the snapshot and resets the boolean, starting a fresh dedup epoch. There is no queue, retry, or cancellation callback for the dropped navigation; idempotency is the design goal (repeating an identical navigation is defined to be redundant). Chromium's 1 s / 2 s / 3 s experiments chose 3 s for latency wins, and a reviewer note (zcorpan) flags that divergent per-browser thresholds would be an interop hazard — one reason the threshold is written into the spec PR rather than left implementation-defined.
Source: issue #11743 discussion; PR #11765 diff.examples
Observing the behavior from a page — the loaded document's timing starts at the first attempt:
// After a double-click navigation to this page, the deduplicated
// second attempt does not restart the clock:
const [nav] = performance.getEntriesByType("navigation");
console.log(nav.startTime, nav.type); // first attempt's start, "navigate"
// There is no web-platform signal that a duplicate was dropped;
// automation sees it via WebDriver BiDi:
// browsingContext.navigationFailed → status: "canceled"
Source: issue #11743 (timing observability); WebDriver BiDi — navigation failed.
browser compatibility
There is no BCD entry or webstatus feature for this deduplication change (checked 2026-07-28) — the table below uses the ChromeStatus ship data for the change itself (interim, labelled), and cites BCD api/Navigation.json for the surrounding Navigation API baseline.
| Browser | Dedup behavior | Evidence |
|---|---|---|
| Chrome | 151+ — Enabled by default | Milestone 151 listing (verified 2026-07-28); kIgnoreDuplicateNavs enabled by default in content_features.cc |
| Edge | No separate signal (Chromium-based) | No public statement recorded on the ChromeStatus entry |
| Firefox | No signal | mozilla/standards-positions #1307 |
| Safari | No signal | WebKit/standards-positions #563; an Apple reviewer raised a network-topology concern in the proposal thread |
| Browser | Navigation API |
|---|---|
| Chrome | 102+ |
| Edge | mirrors Chrome |
| Firefox | 147+ |
| Safari | 26.2+ |
security and privacy
- Deliberately no cookie comparison — comparing cookie state between the two navigations could disclose whether an
HttpOnlycookie changed (an information channel scripts cannot otherwise read), so the design compares no header values at all (annevk, issue #11743). - Stale-content risk is bounded, not zero — a same-URL navigation within 3 s reuses the in-flight response even if server-side state changed; the cookie carve-out covers the common client-triggered case, and the time window bounds the rest.
- Network-topology concern — an Apple reviewer noted the first request may be on a degraded path (e.g. poor cell data just before a wifi reconnect); the design accepts this rather than racing both requests (achristensen07, issue #11743).
- Automation integrity — the BiDi
"canceled"signal exists so tooling does not misread a dropped navigation as a hang.
specifications
| Specification | Status |
|---|---|
| whatwg/html PR #11765 — Navigation: Add optimization to ignore duplicate navigations (preview) | Open (verified 2026-07-28); one-implementer interest so far |
| HTML Standard — navigate | Living Standard (the algorithm the PR modifies) |
| WebDriver BiDi — navigation failed | W3C draft (signal reused by the PR) |
see also
- Chrome Platform Status — Navigation: Ignore duplicate navigations
- whatwg/html issue #11743 — original proposal and design discussion
- W3C TAG design review #1240 (open)
- Chromium tracking bug 366060351
- Mozilla standards position #1307 · WebKit standards position #563 (both no signal)
- Chromium CL 5856811 (browser-side handling; content browser tests in
navigation_controller_impl_browsertest.cc— no upstream WPT files exist for this change as of 2026-07-28) - Chrome Platform Showcase — interactive demos for this feature (duplicate-nav-demo · timing-comparison)
- No public blink-dev intent thread is recorded on the ChromeStatus entry's stage records (checked 2026-07-28)