Back to Knowledge Hub
Platform

Navigation API history and privacy boundaries

A practical, standards-based fixture for same-document and cross-document Navigation API transitions, history state, event ordering, privacy boundaries, and fallbacks.

BotBrowser Team

Documentation

Want the structured docs for Platform?

This article lives in the editorial library. For step-by-step setup, reference material, and ongoing updates, jump into the docs section.

BotBrowser supports repeatable, authorized navigation fixtures in a declared profile and records visible transition and history outcomes. It cannot read another origin's history, infer a person's identity from a URL, circumvent navigation policy, or guarantee that every browser release supports the same Navigation API surface. That distinction matters when a smooth transition looks like a privacy signal: a browser event is an observation of one document and one request, not permission to inspect a user's browsing history.

Language-neutral diagram of same-document and cross-document navigation events, history state, and a privacy boundary.

TL;DR

  • The WICG Navigation API gives a document a structured view of navigations, including same-document URL changes and cross-document commits where supported.
  • currententrychange, navigate, and transition promises describe different points in a navigation; record timestamps and outcomes instead of assuming an event means a commit.
  • navigation.currentEntry and history.state are state for the current document's session history entry, not a cross-origin history database.
  • A fixture must test same-document and cross-document paths separately, then exercise a fallback when window.navigation or a desired event is unavailable.
  • BotBrowser repeats the declared journey and captures bounded evidence. It does not override browser policy, prove universal compatibility, or turn a transition into an identity claim.

Contents

What the Navigation API observes

The WICG Navigation API specification models navigation as an operation with a destination URL, a type, an activation context, and a transition. It is designed to make application navigation observable and controllable in the current browsing context. A same-document navigation, such as a fragment change or history.pushState(), can keep the document alive while changing the active history entry. A cross-document navigation eventually replaces the active document; its destination is not an object that the old document can inspect after it is gone.

MDN's Navigation API guidance is useful for support decisions, but browser support is a moving target. Feature detection is therefore part of the fixture, not an afterthought. Check window.navigation, the event or method you need, and the behavior of the fallback in the same run. An API existing on the global object does not mean every option or transition promise is implemented identically.

The API's value is a structured observation point. A navigate event can expose a NavigationPrecommitController, destination URL, and a way to intercept an eligible navigation. The handler can choose to render application content, call event.intercept(), and use event.transitionWhile() to associate asynchronous work with the browser transition. A test should report whether interception was accepted and whether the transition settled; it should not label a pending promise as a committed page.

The WHATWG HTML history model supplies the underlying concepts: a session history entry has a URL and optional state, and traversal moves the active entry. Navigation API terminology does not remove those rules. A visible route change may be same-document, while a link to another origin normally creates a new document. The distinction affects what event handlers can observe, which state is available, and when the old page stops running.

History entries and privacy boundaries

navigation.currentEntry represents the active entry in the current document's navigation history. Its id, url, and getState() value can help an application restore local UI state. history.state exposes the state associated with the active entry for the same document. Neither API returns a user's complete history, prior private browsing activity, or the history of another origin. Treat the state object as application data with a bounded lifetime, not as an identity token.

State is also not automatically private. A state value can be visible to scripts in the current origin, serialized by application code, or copied into analytics if the application chooses to do so. Do not put access tokens, email addresses, or customer records in pushState() state. Prefer a short route key and fetch authorized data after navigation. A state value surviving a reload or back/forward traversal is a product behavior to test, not evidence that a browser has disclosed anything to a destination.

Same-origin is the relevant boundary for script access. A document can usually inspect its own navigation entries and state, subject to browser policy and lifecycle. It cannot use the Navigation API to enumerate another origin's entries. A cross-origin destination receives a request and runs its own document; the source page's JavaScript cannot continue reading that destination's state. Server logs, referrers, cookies, and application parameters are separate channels and must not be inferred from a transition event.

The privacy-safe interpretation is deliberately narrow: “the current document observed a URL and a state transition.” It is not “the user visited this page,” “the destination knows the previous route,” or “this id identifies a person.” A shared device, prefetch, restore, redirect, or automated test can produce the same browser-visible shape. Keep identifiers synthetic in fixtures and redact query values in evidence.

Event ordering and transition timing

Event ordering is where many Navigation API tests become misleading. A navigate event is an opportunity to observe or intercept a navigation. A successful same-document operation can then update the active entry and lead to currententrychange; a cross-document operation may leave the old document before its later lifecycle events run. The exact sequence depends on navigation type, interception, and browser implementation, so capture the sequence instead of asserting a universal list from memory.

Use monotonic timestamps and a small event log. Record event name, destination URL shape, navigation.currentEntry?.id, navigation.transition?.type when available, and whether a promise fulfilled or rejected. Do not record cookies, authorization headers, full customer URLs, or page text. A useful outcome is “navigate observed at t1; currententrychange observed at t2; transition finished at t3.” A useful failure is “fallback used because navigation was absent,” not “the browser leaked history.”

event.intercept() changes application control over an eligible navigation; it does not grant cross-origin access. event.transitionWhile(promise) lets the browser associate asynchronous rendering work with the transition. Rejecting that promise can produce an aborted or failed transition, while a fulfilled promise is not proof that a server-side action succeeded. Keep network assertions separate: a client transition can finish while an API request is still cached, retried, or rejected.

Back and forward traversal deserve their own cases. A traversal can activate an existing entry and restore state without a new document. A direct cross-document link can instead unload the source and create a destination document. Use a unique synthetic route label for each case, then compare active-entry IDs and state values. Do not assume an identical URL means an identical entry; the history model permits multiple entries with the same URL and different state.

A controlled fixture and fallback

The following Playwright-style fixture keeps the artifact small and privacy bounded. It tests a same-document state update, a back traversal, and a cross-document link on origins that you control. The cross-document page logs only its own load marker; it does not attempt to inspect the source page.

const events = [];
const started = performance.now();
const log = (name, extra = {}) =>
  events.push({
    name,
    atMs: Math.round(performance.now() - started),
    entryId: window.navigation?.currentEntry?.id ?? null,
    urlPath: new URL(location.href).pathname,
    ...extra,
  });

if (window.navigation) {
  navigation.addEventListener('navigate', event => {
    log('navigate', { destinationPath: new URL(event.destination.url).pathname });
    if (event.canIntercept && new URL(event.destination.url).origin === location.origin) {
      event.intercept({
        async handler() {
          log('intercept-start');
          await Promise.resolve();
          log('intercept-end');
        },
      });
    }
  });
  navigation.addEventListener('currententrychange', event => {
    log('currententrychange', { fromId: event.from?.id ?? null });
  });
}

history.pushState({ route: 'synthetic-a' }, '', '/fixture/a');
log('after-push', { stateRoute: history.state?.route ?? null });
await page.getByRole('link', { name: 'cross-document' }).click();
console.log(events);

The fallback must be explicit. When window.navigation or interception is unavailable, use ordinary links and popstate for same-document state restoration, and let the browser perform the cross-document navigation. A fallback is successful when it preserves the product invariant, such as “the route label and authorized view are restored,” not when it imitates every internal Navigation API event. Log the selected mode (navigation-api or history-popstate) alongside the browser version.

Run the fixture in a clean profile and with synthetic paths such as /fixture/a, /fixture/b, and /external/landing. Keep the same-origin and cross-origin origins distinct in the test data. For a cross-origin case, assert only that the destination loaded and that the source did not read destination state. If a server endpoint is needed, record status and a redacted path, never request bodies or account identifiers.

Evidence decision table

ObservationSafe interpretationDo not claimNext action
navigate fired before a same-origin route updateThe current document observed an eligible navigationA server commit or user identityRecord destination shape and outcome
currententrychange shows a new entry IDThe active session-history entry changedThat a new document loadedCompare state and document lifecycle separately
transitionWhile() fulfilledThe associated client work settledThat an API or payment succeededAssert the network result independently
window.navigation is absentThis run needs a compatibility pathUniversal browser incompatibilityExercise link plus popstate fallback
Cross-origin destination loadedThe browser performed a navigationThat source history was readable by destinationVerify isolation with synthetic origins
Same URL has different entry IDsDistinct entries can share a URLOne URL equals one visit or personStore route key and entry ID only in test evidence

BotBrowser supports repeatable, authorized execution of this fixture in a declared browser, platform, proxy, and profile context, with bounded transition and history observations retained for review. Its limits are equally important: it cannot circumvent navigation policy, inspect another origin's history, force an unsupported event, infer a person's identity, or certify a server-side business outcome. The browser and application remain the authorities for those questions.

Designing a useful navigation record.

A navigation record should answer a narrow operational question: which browser-visible transition happened, in which document, and which application fallback ran? Start with a run identifier that is unrelated to an account or URL. Store the browser family and version, the fixture revision, the origin class, and the selected mode. Keep the route values synthetic and bounded, for example fixture/a and fixture/b, rather than copying a customer route into a report. This lets a team compare two browser releases without retaining a browsing diary.

Record the event sequence as an ordered list, but do not treat ordering as a universal contract unless the specification or a product invariant says so. A navigate callback can run before an interception handler, while a currententrychange notification can arrive after the active entry has changed. A cancellation can leave an event in the log even though the destination never commits. Include an outcome field such as committed, aborted, fallback, or unknown, and include the reason for a fallback when it is known. Avoid serializing the complete event.destination.url; a path class and origin class are usually enough for a regression decision.

The fixture should also make a clean distinction between browser evidence and application evidence. Browser evidence includes the event name, entry ID, state key, document lifecycle marker, and visible route label. Application evidence includes a response status, a rendered component, or a successful data request. These can disagree without either layer being broken. For example, a transition may render a cached route while the data request is rejected, or a data request may succeed after a transition was cancelled. A release gate should therefore assert both layers separately and report which one failed.

History state deserves an explicit retention rule. The state object is convenient for restoring a tab, but it is still input controlled by the application. Keep it small enough to reason about, avoid embedding sensitive values, and version its shape when the application changes. On restore, validate the version and route key before using the state. If validation fails, use a neutral landing state rather than attempting to interpret an unknown object. A test should cover an old state version, an empty state, and a state created by a previous route revision. Those cases exercise application recovery while remaining independent of a person's browsing history.

Redirects and reloads are separate cases from traversal. A server redirect can create a new request and a new document without exposing the source document's JavaScript state to the destination. A reload can preserve or replace a history entry depending on the URL and application behavior. A back traversal can activate an entry whose state was created several steps earlier. Include each case in the matrix and use a distinct synthetic marker, such as case=redirect, case=reload, or case=back. Do not use those markers as a fingerprinting signal; they are test labels that should be removed from production telemetry.

When comparing browser releases, hold the fixture, origin configuration, and profile policy constant. Change one variable at a time: the browser build, the Navigation API feature flag, or the fallback implementation. Capture a screenshot or DOM assertion only when it proves a user-visible invariant, and keep it free of real account data. A difference in event timing may be an implementation change without any user-facing regression. Conversely, a stable event sequence does not prove that focus, accessibility announcements, scroll restoration, or form state are correct. Add those user-visible assertions to the application test, not to a privacy conclusion.

Finally, define a stop rule for inconclusive runs. If the browser crashes, the fixture loses its origin isolation, or a required event is missing and no fallback is available, mark the run inconclusive and preserve the minimal diagnostic fields. Do not retry indefinitely or convert an inconclusive result into a compatibility claim. A bounded retry can distinguish a transient test-server failure from a deterministic browser result, but it must have a fixed count and a fresh synthetic profile. This makes the report useful to a release owner without turning it into an inventory of a user's navigation behavior.

The test harness should also state what it deliberately does not measure. A client-side transition does not measure paint latency, input delay, accessibility announcement timing, cache freshness, or the correctness of a server redirect. Those are valid product questions, but they need their own instrumented assertions. Keeping the navigation record narrow prevents an apparently complete event trace from being mistaken for a full performance or privacy audit.

Before each run, clear application data that could alter the route, including service-worker registrations, session storage, and test cookies. Use a fresh synthetic account label when the fixture needs authenticated content, and record only whether authorization succeeded. Do not reuse a profile that contains real browsing history: even when the script cannot read that history, screenshots, redirect behavior, or application personalization can accidentally expose it to reviewers.

For failure diagnosis, preserve the smallest reproducible bundle: browser version, operating system target, fixture revision, selected mode, redacted route class, event names with monotonic times, and the first error category. A HAR file, full console dump, or screenshot should be attached only when it is necessary to explain a user-visible defect and has been scrubbed of credentials and personal URLs. This makes review repeatable without creating a shadow browsing record.

Acceptance should be expressed as explicit invariants. For same-document navigation, require the expected synthetic route and state version after push, back, and forward. For a cross-document case, require a destination load marker and absence of source-state access. For the compatibility path, require the same user-visible route outcome plus a recorded reason for selecting history-popstate. If one invariant fails, report that failure directly; do not soften it by pointing to a successful event elsewhere in the sequence.

Conclusion

Treat the Navigation API as a structured observation surface over one browsing context. Separate same-document state changes from cross-document commits, record event ordering with timestamps, and test a real fallback when feature detection fails. Keep history state synthetic and minimal. The safest acceptance result is a reproducible transition log that says what the current document observed, what it could not observe, and which compatibility path ran.

Next reading

Public sources

#Navigation API#History#Privacy#Same-Document Navigation#Browser Transitions

Take BotBrowser from research to production

The guides cover the model first, then move into cross-platform validation, isolated contexts, and scale-ready browser deployment.