`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).
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.
- Flush pending text in _removeFromParentCallback and
_reparentChildrenCallback. Without these, html5ever can detach or
reparent the pending text node mid-parse and a later flush would write
accumulated bytes onto a node no longer in the tree (or to the wrong
parent).
- Streaming.done now nulls self.handle right after html5ever_finish,
before flushPendingText. If the flush errors the handle is already
cleared, so dropping the Streaming can't double-free.
- Document.close uses a defer to clear _script_created_parser even when
done() returns an error. Document.write's parser-panic path now
attempts a final flush before dropping the streaming parser, so
whatever bytes html5ever fed before the panic still land on their
text node.
- raw_text_chunked.html: larger raw-text bodies and exact byte counts
per element. Catches future deferred-merge regressions that drop or
duplicate a chunk; the memory bound itself is verified out-of-band
via the live reproducer in the PR description.
Refs #2397
Frame.appendNew did String.concat(arena, [existing, txt]) every time
html5ever flushed a script-data/rawtext chunk on a '<' token, allocating
O(N) on the page-lifetime arena per chunk. Total bytes ~= N^2/(2*c). On
apple.com US iPhone pages a 347 KB inline JSON literal with embedded
HTML strings ballooned the parse to 3.5 GB peak RSS.
Move the merge into the parser. Same-parent text chunks accumulate in a
std.ArrayListUnmanaged on the per-parse arena; one String.dupe lands the
final value on the frame arena. Flush points are the natural ends of a
text run: a non-text child appended, a foster/before-sibling insertion,
the parent element popping, or the parse call returning.
Frame.appendNew now takes *Node directly; it had no non-parser callers.
Streaming.done returns !void to propagate the final flush.
Refs #2397
1. Implement document.adoptNode (we were removing from the existing document,
but not adding to the new document)
2. Document.url should use the document's frame, falling back to the execution
frame
3. Move HTMLDocument.location to Document.location
4. DOMImplementation.createDocument uses a more appropriate default namespace
(xml -> null)
5. Map querySelector functions to DOMException-safe errors. The Selector returns
specific errors, but for the DOM apis (document.querySelector,
df.querySelectorAll, elem.matches, etc...) these largely all map to
SyntaxError
1 - Expose various event types for Workers
2 - Listen to the removed listener flag in more places. We delay removing the
listener (to keep the list intact) via a flag, but need to consider that
flag in all places, e.g. when checking for duplicates when adding a listener
3 - Enforce passive flag. We have this flag, but weren't using it to block
calls to preventDefault returnValue (which a passive listener should not
call)