Summary
When Page.createIsolatedWorld is called with the ID of a loaded cross-origin child frame, Lightpanda returns an execution-context ID that evaluates against the main document, not the requested child document.
The behavior reproduces on both the latest stable release (0.3.7) and the current nightly (1.0.0-nightly.9021+27831c20b). Chromium evaluates the correct child document with the same CDP sequence.
The reproduction uses two local HTTP fixture servers on different ports. It makes no requests to third-party websites.
Environment
| Component |
Version tested |
| Lightpanda stable |
0.3.7, tag commit 5b0835f5a17fbb6fd45b56e091a14dded2e887b0 |
| Stable Docker image |
linux/amd64, lightpanda/browser@sha256:eae2c7f8ff39a7a24540d21c7561ce219fd14b1bf2b92403930c6d140f2d1337 |
| Lightpanda nightly |
1.0.0-nightly.9021+27831c20b, commit 27831c20bf50bfd3220a89643e85e192df737ca2 |
| Nightly Docker image |
Created 2026-08-30T02:50:37Z, linux/amd64, lightpanda/browser@sha256:d22c9cc1df1e6f081d90deb0fee96938014262f49406c547cda772982229103a |
| Chromium control |
Playwright 1.62.0 browser: Chrome for Testing 151.0.7922.34, macOS arm64 binary SHA-256 7687bff7cb2db075f250e6d5848bbc8838cac3802ac3952a899c574f8eccab45 |
| CDP client |
github.com/chromedp/chromedp v0.14.2 |
| Go used for the run |
go1.27.0 darwin/arm64 |
| Container runtime |
Docker Desktop engine 28.3.2 |
Nightly disables iframe resource loading by default, so its command below includes --load-resources iframe. Without that flag, the missing child frame is expected and is not the bug described here.
Reproduction
The complete self-contained Go reproducer, dependency manifest, and checksum file are available in this public Gist:
https://gist.github.com/viktor-shcherb/cd1175056d7ac4d755e29d4a21ab6576
git clone https://gist.github.com/cd1175056d7ac4d755e29d4a21ab6576.git lightpanda-iframe-repro
cd lightpanda-iframe-repro
go mod verify
The fixture creates:
- a parent document titled
Parent jobs;
- a cross-origin iframe initially titled
Jobs page one; and
- a second iframe document titled
Jobs page two.
The relevant CDP sequence is:
frameTree, err := page.GetFrameTree().Do(ctx)
if err != nil {
return err
}
mainFrameID := frameTree.Frame.ID
childFrameID := frameTree.ChildFrames[0].Frame.ID
mainContextID, err := page.CreateIsolatedWorld(mainFrameID).
WithWorldName("repro-main-utility").
Do(ctx)
if err != nil {
return err
}
childContextID, err := page.CreateIsolatedWorld(childFrameID).
WithWorldName("repro-child-utility").
Do(ctx)
if err != nil {
return err
}
childTitle, exception, err := runtime.Evaluate("document.title").
WithContextID(childContextID).
WithReturnByValue(true).
Do(ctx)
The program evaluates the child title, changes the iframe from /frame-one to /frame-two, obtains the navigated child frame, creates a new isolated world for it, and evaluates its title again. It also evaluates through the original main-frame context to verify that child navigation did not retarget or invalidate the main world.
Latest stable Lightpanda
docker run --rm --detach \
--platform linux/amd64 \
--name lightpanda-iframe-repro \
--publish 127.0.0.1:9222:9222 \
lightpanda/browser@sha256:eae2c7f8ff39a7a24540d21c7561ce219fd14b1bf2b92403930c6d140f2d1337
go run . \
-backend lightpanda-0.3.7 \
-cdp ws://127.0.0.1:9222 \
-parent-listen 0.0.0.0:18080 \
-child-listen 0.0.0.0:18081 \
-parent-url http://host.docker.internal:18080/ \
-child-origin http://host.docker.internal:18081
Current nightly
docker run --rm --detach \
--platform linux/amd64 \
--name lightpanda-iframe-repro \
--publish 127.0.0.1:9222:9222 \
lightpanda/browser@sha256:d22c9cc1df1e6f081d90deb0fee96938014262f49406c547cda772982229103a \
/bin/lightpanda serve \
--host 0.0.0.0 \
--port 9222 \
--load-resources iframe
go run . \
-backend lightpanda-nightly-27831c20b \
-cdp ws://127.0.0.1:9222 \
-parent-listen 0.0.0.0:18080 \
-child-listen 0.0.0.0:18081 \
-parent-url http://host.docker.internal:18080/ \
-child-origin http://host.docker.internal:18081
Chromium control
"$CHROMIUM_BIN" \
--headless \
--no-sandbox \
--remote-debugging-address=127.0.0.1 \
--remote-debugging-port=9223 \
--user-data-dir="$CHROMIUM_PROFILE" \
about:blank
CHROMIUM_WS="$(curl --silent http://127.0.0.1:9223/json/version | jq -r .webSocketDebuggerUrl)"
go run . \
-backend chromium-151.0.7922.34 \
-cdp "$CHROMIUM_WS" \
-parent-listen 127.0.0.1:18080 \
-child-listen 127.0.0.1:18081 \
-parent-url http://127.0.0.1:18080/ \
-child-origin http://127.0.0.1:18081
Expected behavior
- The main-frame world evaluates
Parent jobs.
- The initial child-frame world evaluates
Jobs page one.
- After child navigation, a newly created child-frame world evaluates
Jobs page two.
- Child navigation does not invalidate or retarget the saved main-frame world.
Chromium produces:
{
"backend": "chromium-151.0.7922.34",
"title_before_child_navigate": "Parent jobs",
"title_after_child_navigate": "Parent jobs",
"child_title_before_navigate": "Jobs page one",
"child_title_after_navigate": "Jobs page two"
}
Actual behavior
Both tested Lightpanda builds return the parent title from execution contexts requested for the child frame.
Lightpanda 0.3.7:
{
"backend": "lightpanda-0.3.7",
"title_before_child_navigate": "Parent jobs",
"title_after_child_navigate": "Parent jobs",
"child_title_before_navigate": "Parent jobs",
"child_title_after_navigate": "Parent jobs"
}
Nightly 27831c20b, with iframe loading enabled, produces the same result:
{
"backend": "lightpanda-nightly-27831c20b",
"title_before_child_navigate": "Parent jobs",
"title_after_child_navigate": "Parent jobs",
"child_title_before_navigate": "Parent jobs",
"child_title_after_navigate": "Parent jobs"
}
The saved main-frame execution context remains valid. The incorrect behavior is specifically that the child-frame contexts resolve to the main document.
Downstream usage context: Jobseek
Jobseek is an open-source crawler that monitors company career pages. It is migrating its crawler runtime from Python + Playwright/Chromium to Go + self-hosted Lightpanda. Browser capabilities that Lightpanda cannot execute remain assigned to the isolated Chromium compatibility backend.
This child-frame functionality is needed by Jobseek's rendered DOM monitor:
- The
hexagon-robotics-careers board configuration identifies an Onlyfy iframe and an in-frame pagination selector.
- The
_execute_repeat implementation evaluates link counts, dispatches the pagination click, and extracts resulting job links inside the child frame.
- The extracted iframe links are injected into the main document so the ordinary DOM link-discovery pipeline can process them.
The concrete board embeds an Onlyfy job list and requires repeated clicks on its in-frame pagination control:
{
"page": "https://robotics.hexagon.com/careers/",
"frame": "iframe[src*=\"onlyfy\"]",
"action": "repeat-click",
"selector": "a.infinite-next"
}
The automation must count links, click a.infinite-next, and extract the resulting links inside the child frame. When the child execution context points to the parent document, the selector is evaluated in the wrong DOM and the interaction cannot be completed. Disabling subframes is not a workaround because the required content exists only inside the iframe.
Until child-frame isolated worlds target the correct document, this configuration must continue using Chromium rather than Lightpanda. That leaves a concrete Chromium-only capability in Jobseek's migration instead of allowing the shared browser executor to route the board to Lightpanda.
Suggested acceptance test
Add a CDP integration fixture with one main frame and one cross-origin iframe and assert that:
Page.createIsolatedWorld returns independently usable contexts for the main and child frames;
- each context evaluates the correct
document.title;
- navigating only the child allows a new child world to evaluate the new child document;
- the original main world still evaluates the main document; and
- the behavior works through direct CDP and drivers that use isolated utility worlds.
Potentially related
Summary
When
Page.createIsolatedWorldis called with the ID of a loaded cross-origin child frame, Lightpanda returns an execution-context ID that evaluates against the main document, not the requested child document.The behavior reproduces on both the latest stable release (
0.3.7) and the current nightly (1.0.0-nightly.9021+27831c20b). Chromium evaluates the correct child document with the same CDP sequence.The reproduction uses two local HTTP fixture servers on different ports. It makes no requests to third-party websites.
Environment
0.3.7, tag commit5b0835f5a17fbb6fd45b56e091a14dded2e887b0linux/amd64,lightpanda/browser@sha256:eae2c7f8ff39a7a24540d21c7561ce219fd14b1bf2b92403930c6d140f2d13371.0.0-nightly.9021+27831c20b, commit27831c20bf50bfd3220a89643e85e192df737ca22026-08-30T02:50:37Z,linux/amd64,lightpanda/browser@sha256:d22c9cc1df1e6f081d90deb0fee96938014262f49406c547cda772982229103a1.62.0browser: Chrome for Testing151.0.7922.34, macOS arm64 binary SHA-2567687bff7cb2db075f250e6d5848bbc8838cac3802ac3952a899c574f8eccab45github.com/chromedp/chromedp v0.14.2go1.27.0 darwin/arm6428.3.2Nightly disables iframe resource loading by default, so its command below includes
--load-resources iframe. Without that flag, the missing child frame is expected and is not the bug described here.Reproduction
The complete self-contained Go reproducer, dependency manifest, and checksum file are available in this public Gist:
https://gist.github.com/viktor-shcherb/cd1175056d7ac4d755e29d4a21ab6576
git clone https://gist.github.com/cd1175056d7ac4d755e29d4a21ab6576.git lightpanda-iframe-repro cd lightpanda-iframe-repro go mod verifyThe fixture creates:
Parent jobs;Jobs page one; andJobs page two.The relevant CDP sequence is:
The program evaluates the child title, changes the iframe from
/frame-oneto/frame-two, obtains the navigated child frame, creates a new isolated world for it, and evaluates its title again. It also evaluates through the original main-frame context to verify that child navigation did not retarget or invalidate the main world.Latest stable Lightpanda
docker run --rm --detach \ --platform linux/amd64 \ --name lightpanda-iframe-repro \ --publish 127.0.0.1:9222:9222 \ lightpanda/browser@sha256:eae2c7f8ff39a7a24540d21c7561ce219fd14b1bf2b92403930c6d140f2d1337 go run . \ -backend lightpanda-0.3.7 \ -cdp ws://127.0.0.1:9222 \ -parent-listen 0.0.0.0:18080 \ -child-listen 0.0.0.0:18081 \ -parent-url http://host.docker.internal:18080/ \ -child-origin http://host.docker.internal:18081Current nightly
docker run --rm --detach \ --platform linux/amd64 \ --name lightpanda-iframe-repro \ --publish 127.0.0.1:9222:9222 \ lightpanda/browser@sha256:d22c9cc1df1e6f081d90deb0fee96938014262f49406c547cda772982229103a \ /bin/lightpanda serve \ --host 0.0.0.0 \ --port 9222 \ --load-resources iframe go run . \ -backend lightpanda-nightly-27831c20b \ -cdp ws://127.0.0.1:9222 \ -parent-listen 0.0.0.0:18080 \ -child-listen 0.0.0.0:18081 \ -parent-url http://host.docker.internal:18080/ \ -child-origin http://host.docker.internal:18081Chromium control
Expected behavior
Parent jobs.Jobs page one.Jobs page two.Chromium produces:
{ "backend": "chromium-151.0.7922.34", "title_before_child_navigate": "Parent jobs", "title_after_child_navigate": "Parent jobs", "child_title_before_navigate": "Jobs page one", "child_title_after_navigate": "Jobs page two" }Actual behavior
Both tested Lightpanda builds return the parent title from execution contexts requested for the child frame.
Lightpanda
0.3.7:{ "backend": "lightpanda-0.3.7", "title_before_child_navigate": "Parent jobs", "title_after_child_navigate": "Parent jobs", "child_title_before_navigate": "Parent jobs", "child_title_after_navigate": "Parent jobs" }Nightly
27831c20b, with iframe loading enabled, produces the same result:{ "backend": "lightpanda-nightly-27831c20b", "title_before_child_navigate": "Parent jobs", "title_after_child_navigate": "Parent jobs", "child_title_before_navigate": "Parent jobs", "child_title_after_navigate": "Parent jobs" }The saved main-frame execution context remains valid. The incorrect behavior is specifically that the child-frame contexts resolve to the main document.
Downstream usage context: Jobseek
Jobseek is an open-source crawler that monitors company career pages. It is migrating its crawler runtime from Python + Playwright/Chromium to Go + self-hosted Lightpanda. Browser capabilities that Lightpanda cannot execute remain assigned to the isolated Chromium compatibility backend.
This child-frame functionality is needed by Jobseek's rendered DOM monitor:
hexagon-robotics-careersboard configuration identifies an Onlyfy iframe and an in-frame pagination selector._execute_repeatimplementation evaluates link counts, dispatches the pagination click, and extracts resulting job links inside the child frame.The concrete board embeds an Onlyfy job list and requires repeated clicks on its in-frame pagination control:
{ "page": "https://robotics.hexagon.com/careers/", "frame": "iframe[src*=\"onlyfy\"]", "action": "repeat-click", "selector": "a.infinite-next" }The automation must count links, click
a.infinite-next, and extract the resulting links inside the child frame. When the child execution context points to the parent document, the selector is evaluated in the wrong DOM and the interaction cannot be completed. Disabling subframes is not a workaround because the required content exists only inside the iframe.Until child-frame isolated worlds target the correct document, this configuration must continue using Chromium rather than Lightpanda. That leaves a concrete Chromium-only capability in Jobseek's migration instead of allowing the shared browser executor to route the board to Lightpanda.
Suggested acceptance test
Add a CDP integration fixture with one main frame and one cross-origin iframe and assert that:
Page.createIsolatedWorldreturns independently usable contexts for the main and child frames;document.title;Potentially related
BrowserContextisolated-world/context-group design. The main context remains stable in this reproduction, but the child-world aliasing may share the same underlying cause.