Commit Graph

5001 Commits

Author SHA1 Message Date
Karl Seguin
89814fd855 Fix ce_reactions audit gaps and iterator-invalidation UAF
popAndInvoke captured the reactions queue as a slice at loop entry. If
firing a reaction triggered a nested scope whose enqueue grew the
underlying ArrayList, the captured slice became dangling and the next
iteration read freed memory. Switch to indexed iteration so the queue
pointer is re-read each step. Repros via wpt/custom-elements/
enqueue-custom-element-callback-reactions-inside-another-callback.html.

The rest of the diff adds .ce_reactions = true to bridge declarations
that were missing it per WebIDL [CEReactions] *and* whose Zig impl
actually performs a DOM/attribute mutation (verified by reading each
setter). Without the flag, the algorithm's enqueue path hits
assertScopeActive and panics. Runtime-state-only setters (Input.value,
Input.checked, Select.value, Option.selected, Media.muted/volume, etc.)
are deliberately left untagged.

Covered:
- CustomElementRegistry.define, .upgrade
- HTMLDocument: body, title, dir
- Document: open, close
- HTMLElement base: insertAdjacentHTML, dir, hidden, lang, tabIndex,
  title, innerText
- Range: insertNode, deleteContents, extractContents,
  surroundContents, createContextualFragment
- Selection: deleteFromDocument
- HTMLOptionsCollection: add, remove
- DOMStringMap (dataset): namedIndexed setter/deleter — required adding
  .ce_reactions to NamedIndexed.Opts in bridge.zig
- ~28 HTMLxxxElement files: every settable attribute that resolves to
  setAttributeSafe/removeAttribute (Anchor, Button, Canvas, Data,
  Details, Dialog, FieldSet, Form, IFrame, Image, Input, Label, Link,
  LI, Media, Meta, OL, OptGroup, Option, Quote, Script, Select, Slot,
  Style, TableCell, Template, TextArea, Time, Track, Video)
2026-05-23 07:52:34 +08:00
Karl Seguin
6eeb97c220 CustomElement Reactions
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/1969
    https://github.com/lightpanda-io/browser/pull/2172
    https://github.com/lightpanda-io/browser/pull/2313
    https://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.
2026-05-23 07:52:32 +08:00
Karl Seguin
aeba861d69 Add WebDriver getComputedLabel
Used by https://github.com/lightpanda-io/wpt/pull/68

Helps many /accname/ WPT tests pass
2026-05-22 20:16:09 +08:00
Karl Seguin
9d6dcb71ab Merge pull request #2521 from lightpanda-io/multi_remove_assert_attributes
Add more attributes to multi_remove assertion failure
2026-05-22 20:12:53 +08:00
Pierre Tachoire
8b2a79d93a Merge pull request #2515 from lightpanda-io/cdp_watchdog 2026-05-22 13:19:48 +02:00
Karl Seguin
38a4a334fd Add more attributes to multi_remove assertion failure
Saw this assertion catch for the first time today. Hoping the extra data will
help identity the issue. No URL or other identifiable data is logged.
2026-05-22 16:10:11 +08:00
Karl Seguin
9621db2e5e Merge pull request #2514 from lightpanda-io/worker_performance
Performance WebAPI on Worker
2026-05-22 12:39:23 +08:00
Karl Seguin
0c9b07ab0f Add exposed option to bridge functions/accessors
While both Window and WorkerGlobalState expose the same "Performance" object,
some getters are window specific (e.g. eventCounts). I thought the same object
would always have the same shape and, when not possible, they'd be different
types (e.g. Location vs WorkerLocation). But that isn't always the case. So
.exposed = .window (or .worker, defaulting to .both) can not be defined on the
bridge mapping.
2026-05-22 11:32:37 +08:00
Karl Seguin
4b5d0109c5 Merge pull request #2513 from lightpanda-io/syncRequest_uaf
Protect against dangling pointer in syncRequest
2026-05-22 11:02:49 +08:00
Karl Seguin
05a373dccc zig fmt 2026-05-22 09:40:06 +08:00
Karl Seguin
5c20a79c08 improve comments 2026-05-22 09:38:58 +08:00
Karl Seguin
c0c1ae286f Merge pull request #2512 from lightpanda-io/about_blank_frame_visibility
Ensure about:blank frame visibility
2026-05-22 09:30:56 +08:00
Karl Seguin
f713d9561b Handle potentially unsorted frames pre-navigate 2026-05-22 08:48:52 +08:00
Karl Seguin
0f11736d3a Update src/browser/Frame.zig
Co-authored-by: Pierre Tachoire <pierre@lightpanda.io>
2026-05-22 08:39:06 +08:00
Karl Seguin
abdfd443e1 On CDP client disconnect, terminate Env
Protects against a stuck worker. This works even if the worker isn't currently
in JS but then enters JS.
2026-05-22 07:13:46 +08:00
Karl Seguin
3fc75dfe85 Revert "Add watchdog to Network thread for abandoned & stuck workers"
This reverts commit 8da7657d4c.
2026-05-22 07:09:57 +08:00
Pierre Tachoire
bc43d64324 Merge pull request #2511 from navidemad/fix-sigterm-live-cdp-connection
network: terminate live CDP connections on shutdown
2026-05-21 19:02:47 +02:00
Navid EMAD
64a6e40121 network: advance shutdownCdpLinks iterator before dropCdp 2026-05-21 15:56:22 +02:00
Karl Seguin
8da7657d4c Add watchdog to Network thread for abandoned & stuck workers
If a worker is in some heavy JS e.g. `for(;;)` it will be stuck forever, even
if the peer closes the CDP connection.

With CDP reads now owned by the network thread, we now correctly detect the
disconnect and simply need to force the worker to shutdown. To achieve this,
on socket close, the CdpLink held by the network is given a terminate_ms (five
seconds from now) and added to a linked list. On every wakeup, the network
thread can check the list + timestamp and, if necessary, call Isolate::Terminate
(which is safe to call on a different thread).
2026-05-21 19:22:12 +08:00
Navid EMAD
3bfc434b3b network: fix typo in shutdown comment (Idempotent) 2026-05-21 12:40:48 +02:00
Navid EMAD
baf1b20532 network: free request headers when syncRequest short-circuits on disconnect
syncRequest's `terminated` early return (added in 88b98e70) exits before request() takes ownership of req.headers, so the curl_slist leaked after a latched disconnect (any nested fetch / importScripts / external stylesheet). Free it in the early return, mirroring request()'s own failure paths.

Also adds a behavioral syncRequest-after-disconnect test. The leak itself isn't assertable here (curl_slist is C-allocated through the tracking allocator, outside the per-test leak check), so it's verified by the ownership contract rather than the suite.
2026-05-21 12:36:45 +02:00
Navid EMAD
901eece853 network: add regression test for the CDP disconnect latch (#2510)
Drives a .disconnect through the worker's inbox and asserts a second tick still returns error.ClientDisconnected once the inbox is empty. Fails against the pre-latch HttpClient (the worker would re-park in perform with no producer to wake it); passes with the latch.
2026-05-21 12:18:28 +02:00
Karl Seguin
972be65db7 Merge pull request #2505 from lightpanda-io/transfer_state
Cleanup Transfer flag
2026-05-21 17:42:00 +08:00
Karl Seguin
24cc29ee79 Performance WebAPI on Worker
This includes EventCounts and PerformanceObserver. This change is like any
other WebAPI on Worker change (e.g. Frame -> js.Execution), but we need the
performance observer hooks in addition to that. Thankfully, every Frame/Worker
only has 1 Performance instance, so we can move the observers and delivery
from the Frame to the Performance instance so that both Frame and WGS gain the
behavior.
2026-05-21 16:17:21 +08:00
Karl Seguin
f3421466cd Protect against dangling pointer in UAF
syncRequest creates a context that lives on the function's stack. This "works"
because the transfer is only expected to live during the call to syncRequest
(this is the entire premise of syncRequest). But if self.tick returns an error,
the function returns, potentially with transfer.req.ctx still pointing to the
stack value.

This change captures a tick error and, if the sync request is still in_progress,
aborts the transfer. There's maybe a world where the error is recoverable and
the request could continue, but that seems error prone. AND, the most likely
non-transfer error that tick would return are pretty "serious", e.g. OOM or
client disconnected.
2026-05-21 14:23:58 +08:00
Karl Seguin
ca0eaa5f1e Ensure about:blank frame visibility
Our navigate on about:blank short-circuits most of the complex logic and loads
the content then and there, not asynchronously. This results in notifications
for the frame navigation/creation that happen immediately. With the current
code ordering though, the frame isn't entered in child_frames until AFTER
navigation, resulting in these events trigger on a frame which can't be found
via Session.findFrameByFrameId

This code adds the frame to child_frames BEFORE navigate (and removes it on
error).

Note: about:blank iframe navigation is more common than you probably think. As
soon as a frame is [dynamically] created, it navigates to about:blank, e.g.:

let iframe = document.createElement('iframe');
// IMPLICIT navigation to about:blank happens here

// and this will be another navigation event
iframe.src = "keemun.php";
2026-05-21 12:33:50 +08:00
Karl Seguin
666ff0b670 Remove unused intercept_response ParkedBy
Add comment about how unpark is meant (and not meant) to be used.
2026-05-21 11:50:25 +08:00
Karl Seguin
88b98e705f Capture disconnect/close in Worker
Currently, if a disconnect/close is captured in a worker during a syncRequest,
that specific request is terminated, but the error doesn't bubble up. The worker
remains alive and will subsequently block in a perform, with no connection alive
to wake it up.

In this commit, when disconnect/close is received, inbox.terminate is set to
true. This flag is checked (in syncRequest and http_client.tick) and
error.ClientDisconnected is returned.

(Also, on network shutdown, always broadcast the cdp_unregister since there's
no harm in sending an extra signal even if nothing was removed).
2026-05-21 10:38:52 +08:00
Karl Seguin
e4171bc694 Merge pull request #2501 from lightpanda-io/remove_reentrency_teardown_protection
Remove reentrency teardown protection
2026-05-21 10:15:26 +08:00
Karl Seguin
4c454cff71 Merge pull request #2509 from navidemad/fix/network-pollfds-clobbers-cdp-sockets
Fix CDP server stall/SIGTERM hang in optimized builds (Network drops CDP sockets from poll set)
2026-05-21 06:56:46 +08:00
Navid EMAD
bdf28c51cd network: terminate live CDP connections on shutdown
The CDP server ignored a single SIGTERM while a connection was live: the
process only exited if the socket was closed before the signal, or after a
third signal. A conventional one-shot graceful stop (SIGTERM then waitpid)
hung.

On shutdown the sighandler runs Network.stop (which sets `shutdown` and lets
the run loop exit) before Server.shutdown. A live CDP worker parks in
curl_multi_poll and is woken ONLY by the Network thread via
dropCdp -> handles.wakeup(). Once the run loop exits with links still live,
nothing can wake those workers, so Server.deinit()'s
`while (active_threads > 0)` loop spins forever.

Drop every still-live CDP link from the run loop when `shutdown` is set,
reusing the existing peer-EOF path: dropCdp(notify=true) pushes a .disconnect
into the worker's inbox and wakes it, so cdp.tick() returns false and the
worker exits before the loop breaks.
2026-05-20 21:01:51 +02:00
Navid EMAD
e1e49c8a2e Fix Network dropping CDP sockets from its poll set once a multi exists
preparePollFds cleared and rebuilt the curl portion of `pollfds` every
loop iteration, but sliced `pollfds[PSEUDO_POLLFDS..]` — all the way to
the end of the array. That range also covers the CDP socket region
`[cdp_start..]`, which prepareCdpPollFds owns and only rebuilds when
`cdp_dirty` is set (a steady-state optimization). So the @memset wiped
every live CDP socket fd to -1 on each iteration.

This only bites once Network owns a curl multi handle, which is created
solely by telemetry — and telemetry is disabled in Debug builds, which
is why it reproduced only in ReleaseFast/ReleaseSafe (and the nightly).
Regular HTTP/navigation runs on the worker's own handles, not Network's
multi, so it never triggered the path in Debug.

Once the CDP sockets are dropped from the poll set, the Network thread
stops reading client messages (#2508, hard stall after the first
command) and never observes peer EOF or `conn.shutdown`, so the worker
is never told to exit and SIGTERM is ignored after a connection (#2507).

Fix: slice only the curl region `[PSEUDO_POLLFDS..cdp_start]`.

Also harden the poll timeout: `curl_multi_timeout` returns -1 when curl
is idle, and `@min(250, -1)` is -1 (block forever), which starved
onTick (telemetry's periodic flush) and turned any missed wakeup into a
permanent hang rather than a <=250ms blip. Treat curl_timeout <= 0 as
"no deadline" and fall back to the 250ms cap.

Fixes #2507
Fixes #2508
2026-05-20 19:13:09 +02:00
Pierre Tachoire
5fce406a13 compact more help options 2026-05-20 16:09:16 +02:00
Pierre Tachoire
af6175cc56 rewrite help in a more compact way
Inspired by go help output
2026-05-20 16:05:17 +02:00
Francis Bouvier
dffa961f45 Remove options from main help 2026-05-20 16:05:13 +02:00
Karl Seguin
1cdd2bb324 Cleanup Transfer flag
Replaces 4 boolean flags with a state. Makes it easier to figure out what the
state of the transfer is, and removes the possibility of inconsistent flags
.e.g queued + loop_owned.

loop_owned -> state != .created
_queued -> state == .queued
_perform -> state == .completing
aborted -> state == .aborted
2026-05-20 20:37:21 +08:00
Pierre Tachoire
f1b0adf923 Merge pull request #2498 from navidemad/fix/author-display-rule-beats-ua-hidden
`StyleManager`: author `display` rule must beat UA `[hidden]` / `display:none`
2026-05-20 14:01:25 +02:00
Pierre Tachoire
bb2d62d189 Merge pull request #2500 from lightpanda-io/parseHtmlAsChildren_assertion
parseHtmlAsChildren handling for unexpected dom (custom element callb…
2026-05-20 14:00:58 +02:00
Pierre Tachoire
6e6b3caf96 Merge pull request #2479 from navidemad/accessibility-query-ax-tree
Implement Accessibility.queryAXTree CDP method (and fix latent frame-binding bug)
2026-05-20 13:59:35 +02:00
Pierre Tachoire
2ad2c9d878 Merge pull request #2487 from navidemad/feat/external-stylesheets-flag
Add --enable-external-stylesheets flag with fetch + parse
2026-05-20 13:41:59 +02:00
Karl Seguin
b6fd09c5ab Merge pull request #2502 from lightpanda-io/max-cdp-conn
by using httpClient, fetch generates a call to Config.maxConnections
2026-05-20 16:28:56 +08:00
Pierre Tachoire
639cb14cb3 Merge pull request #2494 from marchelbling/feat-fetch-json-option
feat: add --json to fetch command
2026-05-20 10:17:21 +02:00
Pierre Tachoire
6eb25d5c44 by using httpClient, fetch generates a call to Config.maxConnections 2026-05-20 10:14:17 +02:00
Karl Seguin
a9cf87e0b0 Remove reentrency teardown protection
This largely reverts 92607ad765 (captured in PR:
https://github.com/lightpanda-io/browser/pull/2398).

https://github.com/lightpanda-io/browser/pull/2495 introduces protection against
execution arbitrary CDP command during JavaScript callbacks. Claude initially
made the case for keeping the existing code as a safety net, but sycophanted
when I pushed by.

My reason for removing it is that it isn't a low-maintenance guard. It's a flag
that serves a real purpose (ensuring 1 JS script is finished before executing
another one), that has been expended to solve these issues. It needs to be set
(and reverted) at every callsite that makes a blocking call, and it needs to be
checked (recursively across all frames) in any place that can teardown the page/
frame.

Claude called the allowlist "load-bearing in a non-obvious way", but I think
it's purpose built specifically for this case. Extended the comment atop
`allowDuringSyncWait` so that future-selves remember this.
2026-05-20 15:08:18 +08:00
Karl Seguin
a314984b2e Merge pull request #2495 from lightpanda-io/cdp_inbox
Main/Network reads CDP socket
2026-05-20 14:35:29 +08:00
Karl Seguin
5fb0f5a204 parseHtmlAsChildren handling for unexpected dom (custom element callback)
Removes an assertion that can break with custom element callbacks.
https://github.com/lightpanda-io/browser/pull/2429 does not solve this issue
since it isn't a result of when reactions are executed, but just that they
happen.

(I should note, that I'm not 100% sure the above statement is correct. It's
possible that our CE reactions are (in some cases at least) too micro. Maybe
some operations, like setInnerHtml should operate within a more atomic
frameworks, vs an CE-reaction per internal step. But that's a pretty big change)
2026-05-20 14:33:03 +08:00
Karl Seguin
e7b16983bb Add help documentation + small tweaks
This adds help documentation for the --json flag. This is the only thing that
must be kept from this commit.

It uses our testing.zig to streamline the json testing (instead of string
probing). It removes the JsonEnvelope in favor of an anonymous struct (though,
I'm fine with adding it back in if it's needed to resolve something ambiguous).

Finally, I removed the last unit test as, at that point, it's really just
testing Zig's JSON stringifier (could arguably make the same case for the other
two, but there's some logic there about how nulls/empty might be handled).
2026-05-20 10:44:41 +08:00
Karl Seguin
2fd2be0e51 Small largely stylistic tweaks
Trim down a lot of the comments. Inline remarks in some cases rather than a
large function header.

Removing the `errdefer headers.deinit();` is unfortunate but currently
necessary to avoid a potential double-free if the request gets far enough that
http_client frees it and still returns an error. This is a known issue that
needs to be fixed separately and that impacts multiple call-sites. My "fix"
introduces a possible (very small) memory leak versus a possible crash.
2026-05-20 10:31:28 +08:00
Karl Seguin
61386059c1 promote invalid CDP message from warn to err 2026-05-20 07:19:57 +08:00
Karl Seguin
037db695ff Merge pull request #2492 from lightpanda-io/cdp_connection
Re-organization CDP connection
2026-05-20 06:45:30 +08:00