Commit Graph

6395 Commits

Author SHA1 Message Date
Armaan Sandhu
a7e3bea672 feat(webapi): implement W3C File API 2026-05-24 13:04:33 +05:30
Karl Seguin
93d2e9689b Merge pull request #2522 from lightpanda-io/WebDriver_getComputedLabel
Add WebDriver getComputedLabel
2026-05-23 07:49:44 +08:00
Pierre Tachoire
9619738111 Merge pull request #2517 from navidemad/makefile/download-v8
Add `make download-v8` to fetch prebuilt V8 and skip the source build
2026-05-22 15:53:48 +02:00
Navid EMAD
47bebded55 Point download-v8 at the cache directly, drop the v8/ symlink
The prebuilt archive already lives at .lp-cache/prebuilt-v8/<version>.a;
the v8/libc_v8.a symlink was redundant indirection. build.zig has no
implicit lookup, so the Makefile passes -Dprebuilt_v8_path explicitly
anyway. Point it straight at the cached file, dropping the symlink step,
the v8/ dir, and its .gitignore entry. Keep the version-keyed filename so
bumping the V8 pin fetches a new file instead of clobbering.
2026-05-22 15:16:57 +02: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
Navid EMAD
fc795c3b56 Document ZIGFLAGS= make download-v8 in CONTRIBUTING 2026-05-21 14:44:50 +02:00
Navid EMAD
daa8fff5f3 Add ZIGFLAGS= make download-v8 to fetch prebuilt V8 and skip the source build 2026-05-21 14:39:25 +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
5905319e78 Merge pull request #2503 from lightpanda-io/general_help
Remove options from main help
2026-05-20 16:25:39 +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