lightpanda-io / lightpanda-io/browser

CDP: Page.navigatedWithinDocument is never emitted for same-document navigations (history API / fragment / traversal)

Open
#2,829 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Zig
Stars
35.5k
Forks
1.7k
Avg merge
21h 13m
Merged PRs (30d)
262

Description

Summary

Lightpanda performs same-document navigations correctly in-page (history.pushState/replaceState update window.location; fragment changes and popstate work), but it never emits the CDP Page.navigatedWithinDocument event. As a result, any CDP client (Playwright/Puppeteer) never learns about in-document URL changes: the frame's tracked URL goes stale and SPA route changes are invisible to automation. Full-document navigations are unaffected (they correctly emit Page.frameNavigated).

Environment

  • lightpanda serve, driven over CDP. Reproduced on 1.0.0-dev; the gap is present at current HEAD.
  • Client: Playwright 1.59 via chromium.connectOverCDP('ws://127.0.0.1:9222').

Expected vs actual

A real Chromium fires Page.navigatedWithinDocument { frameId, url } whenever a same-document navigation happens — history.pushState/replaceState, history.back/forward/go within the same document, fragment (#) navigation, and same-document anchor clicks. Playwright uses this to update frame.url() / page.url().

Lightpanda: the in-page location updates, but no CDP event is sent, so page.url() stays at the pre-pushState value indefinitely, and expect(page).toHaveURL(...) / page.waitForURL(...) never observe the change.

Minimal repro

await page.goto('http://x.test/a');                       // mock via page.route
await page.evaluate(() => history.pushState({}, '', '/b'));
// Expected: Page.navigatedWithinDocument fires, page.url() === 'http://x.test/b'
// Actual:   no event; location.pathname is '/b' (in-page OK) but page.url() stays '/a'

Same result for location.hash = '#x' and for history.back() after a pushState.

Real-world impact

SPA frameworks (e.g. react-router v7 createBrowserRouter) route client-side via history.pushState. Because the route change is never reported over CDP, automated tests can't assert on the URL after navigation. Anchor-click navigation itself works (it does an async full-document load via scheduleNavigation, which emits Page.frameNavigated); the gap is specifically the same-document path.

Where it's missing in the source

Three code paths change frame.url in place without a document reload, and none emit a CDP event (grep -rn navigatedWithinDocument src/ returns nothing):

  1. src/browser/webapi/History.zigpushState / replaceState set frame.url + reinit window._location.
  2. src/browser/Frame.zig — the same-document fragment short-circuit in scheduleNavigationWithArena (anchor clicks, location.hash).
  3. src/browser/webapi/navigation/Navigation.zignavigateInner's same-document .push / .replace / .traverse branches (the Navigation API and history.back/forward/go).

By contrast, full-document navigations dispatch .frame_navigatedsrc/cdp/domains/page.zig:frameNavigatedPage.frameNavigated. There is no same-document analogue.

CDP shape to emit

Page.navigatedWithinDocument
  frameId: <main frame id, same one used for Page.frameNavigated>
  url: <new absolute URL>
  navigationType: "historyApi" | "fragment" | "other"

Implementation

I have a fix (with CDP-level regression tests, full suite green):
https://github.com/bebsworthy/browser/commit/8a51a0b830ad3125c36ea9298f868db6387d7851
(branch: fix/cdp-navigated-within-document)

A new Notification.FrameNavigatedWithinDocument event (frame_id, url, navigation_type) is dispatched from the three paths above via a Frame.notifyNavigatedWithinDocument helper and handled in page.zig, which emits Page.navigatedWithinDocument { frameId, url, navigationType }. Unlike frameNavigated, it does not clear/recreate the execution context or send DOM.documentUpdated — the document is unchanged, matching Chrome. navigationType maps to historyApi (pushState/replaceState, Navigation API, traversal) or fragment (anchor / location.hash).

Related issues

  • #1401 (open) — programmatic click runs but page.url() unchanged over CDP (closest existing match).
  • #2043 (open) — CDP automation roadmap; lists "pushState updates location" but tracks no navigatedWithinDocument event.
  • #1798 (open) — SPA/Web-API support in progress.
  • #2131 (merged) — made pushState update location JS-side, but added no CDP event (the JS-side precedent this builds on).

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the three named paths: src/browser/webapi/History.zig, src/browser/Frame.zig, and src/browser/webapi/navigation/Navigation.zig, then inspect the referenced commit and its CDP-level regression tests. Done means same-document URL changes emit Page.navigatedWithinDocument with the frame ID, absolute URL, and navigation type without recreating the document context.

Written by the indexing model from the issue text.

Assessment

Tech stack
playwright, zig
Domain
api, backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.