The frameNavigated CDP handler sent Page.frameNavigated twice per
navigation and always used the root frame's V8 context for
inspector.contextCreated, even during iframe navigations. This caused
the root frame's inspector context id to be silently re-registered
(and the previous id invalidated) whenever an iframe navigated.
The duplicate Page.frameNavigated masked this bug — Puppeteer happened
to pick up the re-registered context id. Removing the duplicate exposed
the underlying issue: callFunctionOn with the original id failed with
"Cannot find context with specified id".
`lightpanda <subcommand> help`, `lightpanda <subcommand> --help`
now print only the relevant subcommand options plus common options,
instead of the full text.
`lightpanda help <subcommand>` is also supported
(and that's what use internally).
Reciprocal pointers so humans landing in CONTRIBUTING.md discover
the agent/operational conventions in AGENTS.md, and agents landing
in AGENTS.md discover the CLA gate and pre-PR checks. Adds a short
Development section (test + fmt commands) and a Before-opening-a-PR
checklist to CONTRIBUTING.md; CLA paragraph preserved verbatim and
moved to its own section.
While this PR touches a lot of files, and isn't trivial, many of the changes
are either:
1 - removing guards added in previous PRs, e.g.
https://github.com/lightpanda-io/browser/pull/1969https://github.com/lightpanda-io/browser/pull/2172https://github.com/lightpanda-io/browser/pull/2313https://github.com/lightpanda-io/browser/pull/2366
2 - Adding the `.ce_reactions = true` flag to various WebAPIs
CustomElements have callbacks, e.g. connectedCallback. Also, many WebAPI calls
are implemented as a series of mutations, e.g. appendChild = remove from current
+ append to new.
These two things interact in an important way: when should callbacks execute?
Before this PR, we were invoking callbacks at each individual step. This is
(a) technically wrong and (b) breaks a lot of assumptions (the reason the above
4 PRs were needed to fix bugs).
This PR adds a `_ce_reactions` queue to the frame. And, instead of invoking
callbacks, we "enqueue" the reaction. At various boundaries, a scope is created
the DOM manipulation is done, and then we pop the scope, invoking all queued
reactions.
Addresses follow-up review from karlseguin on #2406.
The pending-text merge buffer is now a single ArrayList on the Parser,
reused across runs via clearRetainingCapacity. In the streaming-parser
case (Document.write), parser.arena is the page-lifetime frame.arena, so
the previous per-PendingText buf.deinit was a no-op and growth artifacts
accumulated. With one shared buffer, total dead memory is bounded to one
peak-run-sized allocation regardless of how many text runs the parse
contains.
Single-chunk text runs no longer touch the buffer. The first chunk lives
only on CData._data via createTextNode; the buffer is seeded from
text_node.getData().str() only when a second chunk arrives at the same
parent and last_child. flushPendingText is a no-op when the buffer is
empty. Restores the common-case allocation count to 1 (matching main),
vs 3 in the previous PR head.
Benchmark deltas (ReleaseFast, peak RSS, 5-run median):
- 10K-paragraph synthetic page: 39 MB -> 37 MB
- 20K single-chunk script synthetic: 56 MB -> 54 MB
- 100 x 48 KB multi-chunk scripts: within noise (~46 MB)
- apple.com US iPhone live page: within JS-driven noise (~92 MB)
Refs #2397
* Prefer `--inject-*` prefix.
* Support injecting multiple scripts (also allows using both variants together).
* Instead of executing scripts in JS context, actually insert them to `<head>` for correct dump output.