Commit Graph
57 Commits
Author SHA1 Message Date
Muki Kiboigo fa71299c75 createTarget should navigate to about:blank initially 2026-09-09 07:52:02 -07:00
Muki Kiboigo a6d0ecb790 add cdp target test that tries to read history.state 2026-09-09 07:51:46 -07:00
Halil Durak 56c1694175 WS: guard against integer overflow when parsing message lengths
Tried to not to change function signature by relying on saturating addition; could've implemented differently, though I'm not sure if returning an error here would make a huge difference.
2026-09-08 10:46:25 +03:00
Karl Seguin 6904e9ee49 Merge pull request #3397 from lightpanda-io/isolatedworld-frames
CDP: create an context per isolated world per frame
2026-09-07 14:53:49 +08:00
Karl Seguin eaf809d46e remove out of date comment 2026-09-07 08:18:50 +08:00
Navid EMAD ed2a5c6eeb webapi: move the text entry cursor on caret movement keys
A trusted keydown for ArrowLeft/ArrowRight/Home/End (and ArrowUp/ArrowDown
on a single-line <input>) fell through Frame.user_input.editKey, which only
handled Backspace/Delete and printable keys, so the caret never moved, Shift
never extended the selection, and a plain arrow never collapsed one.
innerInsert's no-selection arm also appended to the end of the value instead
of inserting at the caret. Add a moveCaret helper to the shared text entry
mixin (byte offsets stepping over whole UTF-8 sequences, line moves bounded
by '\n'), route the keys to it from editKey, and insert at the caret.

Closes #3423
2026-09-07 08:16:52 +08:00
Karl Seguin b21ec60853 Merge pull request #3411 from lightpanda-io/remove-unused-imports
chore: remove unused imports
2026-09-07 07:20:50 +08:00
Karl Seguin 80f057c741 Merge pull request #3422 from navidemad/fix-a55-textarea-selection-default-value
webapi: clamp text entry selections against the current value
2026-09-07 07:20:31 +08:00
Navid EMAD 172614d138 webapi: clamp text entry selections against the current value
`select()`, `setSelectionRange()` and `howSelected()` in the TextEntry mixin
read the `_value` slot directly. That slot only holds an *assigned* value, so
a `<textarea>abcdef</textarea>` straight out of the parser has none and every
selection call collapsed the caret to 0 — while `textarea.value` returned
"abcdef" all along, since `getValue()` falls back to the child text node.

Route the three sites through `getValue()`, matching `innerDelete()`, which
already did. Per HTML, "set the selection range" clamps against the element's
API value, and a textarea's API value is its raw value.

`howSelected()` now also clamps: `selectionStart`/`selectionEnd` store their
argument verbatim, so an offset can outlive a shorter value and reach
`innerInsert()`'s `.partial` arm as an out-of-range slice index.

Closes #3421
2026-09-07 06:48:48 +08:00
Navid EMAD ae6a646797 link test: check the O_NONBLOCK bit, not the whole F_GETFL word
macOS adds an internal 'was written' bit to F_GETFL once the fd has been
written to, so the exact comparison fails there (expected 6, found 65542)
even though the send timed out as intended and O_NONBLOCK is still set.
2026-09-06 20:06:27 +08:00
Karl Seguin 6029d98134 Merge pull request #3416 from navidemad/fix/server-tests-macos-loopback
server tests: wait for loopback readiness on macOS
2026-09-06 20:01:33 +08:00
Karl Seguin faad885913 improve macos test stability 2026-09-06 18:13:08 +08:00
Navid EMAD c9d79fc46b server tests: wait for loopback readiness on macOS
macOS delivers loopback traffic asynchronously, so LoopTest.accept could
call Server.accept before the handshake ACK landed (NotAccepted) and the
tests could read before the request bytes landed (WouldBlock, RST, and a
double disconnect that cast a -1 socket to usize in KQueue.socketEvent).
Poll for readiness first. Also accept any non-zero SO_KEEPALIVE: BSD
getsockopt returns the option bit, not 1.
2026-09-06 18:13:08 +08:00
Navid EMAD 670ca1641e webapi: make trusted beforeinput cancelable
InputEvent.initWithTrusted overwrote _bubbles/_cancelable/_composed for
every InputEvent right after Event.populatePrototypes had applied the
caller's options, forcing _cancelable = false. user_input.zig#allowEdit
asks for a cancelable beforeinput so a listener can veto the edit, but
preventDefault() is a no-op on a non-cancelable event, so the character
was inserted and `input` fired regardless.

Guard the flag block with `if (trusted)` — the shape KeyboardEvent
already uses — and derive _cancelable from the event type: `beforeinput`
is cancelable, `input` is not. Constructed events and
document.createEvent('InputEvent') now follow the EventInit dictionary
defaults instead of being forced to bubbling and composed.

Closes #3413
2026-09-06 04:15:41 +02:00
Karl Seguin a5e27869d3 chore: remove unused imports 2026-09-05 17:51:47 +08:00
Karl Seguin efe406e2bb fix name ownership, set isolated world context origin 2026-09-05 17:33:04 +08:00
Karl Seguin 3165fa8c82 Merge pull request #3407 from staylor/staylor-3319-watchdog-termination-segv
Disarm watchdog before protocol teardown
2026-09-05 17:32:32 +08:00
Karl Seguin 77594b7ce4 Merge pull request #3378 from lightpanda-io/matchmedia-change-events
webapi: matchMedia change events and real (add|remove)Listener
2026-09-05 16:03:35 +08:00
Karl Seguin 6b1f77981f tie MediaQueryList to the frame, not the page 2026-09-05 15:45:41 +08:00
Scott Taylor ecc9cffa12 fix: disarm watchdog before protocol teardown 2026-09-04 13:18:37 -04:00
Muki Kiboigo 6bf77ad0f2 make test client buf larger to accomodate larger metrics 2026-09-04 07:02:02 -07:00
Muki Kiboigo 8a8bb3814f fix test running 2026-09-04 07:00:01 -07:00
Muki Kiboigo c3a71efca4 include modes on EchoDriver 2026-09-04 07:00:01 -07:00
Muki Kiboigo 1fe8456cbd add modes to the tests 2026-09-04 07:00:01 -07:00
Karl Seguin 396cffa1c1 Merge pull request #3391 from lightpanda-io/resource-timing
webapi: resource-timing
2026-09-04 18:03:17 +08:00
Karl Seguin 8937a7b4e0 cdp: Give isolated worlds a context per frame
Previously, we had a single root-bound context which we'd re-announce for every
frame. Now, Page.createIsolatedWorld and addScriptToEvaluateOnNewDocument seed
the context per frame(s).
2026-09-04 11:01:20 +08:00
Karl Seguin 8898d08045 null driver 2026-09-04 02:54:14 +08:00
Karl Seguin 87320a506d chore: Move ownership of the Inbox from HttpClient to WebSocker Driver
Currently, the HttpClient owns the inbox and its borrowed by the Link. This is
a bit backwards, but it also means that we can't eagerly create a Link: the
Link needs the inbox, so it needs the HttpClient, which is created by the
Browser (which creates an Isolate).

Remember, the Inbox is one of the few things shared between the main thread
and the worker, so either end can own it and the other can borrow it.

This switches the ownership so that the HttpClient now borrows the Inbox from
the Server's side of the Link (the WebSocket).

The main goal of this change is to prepare for more advanced HTTP WebDriver
flows. The more we can create _without_ a Browser, the fewer edge cases we have
to deal with (Browser because it's expensive and has to be created on the
Worker thread due to how V8::Isolate works).
2026-09-03 16:51:12 +08:00
Karl Seguin 7263acebcd CDP: create an context per isolated world per frame
Previously, we created a single context per isolated world for the main frame.
We now create an isolate world per frame.

Fixes: https://github.com/lightpanda-io/browser/issues/3347
2026-09-03 16:37:55 +08:00
Karl Seguin c189d72adf add safer shutdown 2026-09-03 07:11:26 +08:00
Karl Seguin 7f29bf6931 500 on write error and remove blocking writes 2026-09-03 06:47:53 +08:00
Karl Seguin 17c2aa7d1f Hardening
Give it one pass through some Claude fuzz testing. Add a max message size,
protect against weird interactions during a shutdown and we had some pending
accepts. Put a time limit on blocked writes.
2026-09-03 06:47:53 +08:00
Karl Seguin 116dab4c85 mac/bsd support (kqueue) 2026-09-03 06:47:53 +08:00
Karl Seguin e6241c1918 serve: Improve cdp/bidi server
Significant rework of the CDP/BiDi server. There are two main changes:

1 - poll replaced with EPoll/Kqueue (1)
2 - make http serving a first class citizen

The change from poll -> epoll/kqueue isn't performance driven, it's just about
tighter code. Both epoll and kqueue let you associate arbitrary data with a
socket, so we don't need to keep arrays in sync in order to associate a socket
with a CDP by index. They both provide some event/notification mechanism, which
is cleaner than the pipe required by poll.

The poll -> epoll/kqueue change could almost have been mechanical. Making HTTP
a first class citizen is the more significant of the two changes

In `main`, a new connection always spawns a thread and, until does its own
little read loop until the connection is upgraded. This is not efficient, it
uses up a connection slot, and it's inconsistent with the final WebSocket
connection which _is_ polled off the main loop. Using up a slot means that
keepalive isn't possible, else HTTP connections would quickly use up all
available slots/threads.

This commit parses and serves HTTP requests on the main thread (safe
because none of the processing is blocking). The approach is better streamlined
for HTTP requests which never upgrade (/metrics, WebDriver) without causing
any performance overhead for those that do. It simplifies some things (e.g. an
"http" socket or a "websocket" socket is monitored and read in a similar manner
(on the main loop)). It makes other things more complicated; the flow is no
longer accept -> spawn -> upgrade -> websocket loop. It's loop -> accept -> loop
-> process -> (http | ws).

This is built ontop of the BiDi branch because (a) WebDriver is what needs
better  HTTP support and (b) some of the more mechanical changes already exist
in that branch (e.g. src/cdp/, src/server.zig -> src/server/*)

(1) kqueue landing in 2 commits from now on this branch.
2026-09-03 06:47:49 +08:00
Karl Seguin 872fd7d5ff webapi: resource-timing
Adds resource timing, e.g. `performance.getEntriesByType("resource")`.

The `resource-timing` WPT category is currently at 4.6%, and this is a first
step at improving it. It also hopefully fixes https://github.com/lightpanda-io/browser/issues/3359

This is more complicated than I thought because there's a "Timing-Allow-Origin"
header that a server can include which hides some of the data if the request
doesn't come from the listed origin. And that, of course, interacts with
redirects.

(The DOMException change is seemingly random, but it came up in one of the WPT
cases I was looking at).
2026-09-02 20:39:51 +08:00
Karl Seguin 2474a2c2bc Merge pull request #3368 from lightpanda-io/window-stop
webapi: window.stop()
2026-09-02 09:06:07 +08:00
Karl Seguin ed88bd763f complete merge 2026-09-02 08:17:15 +08:00
Karl Seguin e25d2da0c0 chore: Simplify making HTTP requests.
If you look at https://github.com/lightpanda-io/browser/pull/3293, you'll see
a relatively contained change that has to touch over 20 files. The issue is that
every HttpClient.newRequest needs to provide a lot of data. But `newRequest`
takes a 2nd parameter: the HttpClient.Owner. If we make that Owner a little
smarter, we can start to remove some of the individual fields needed in
newRequest. For example, we can still allow a callsite to pass frame_id but,
by default, we can use the owner's frame_id (which is what we want in most
cases).
2026-09-02 07:55:53 +08:00
Karl Seguin 07198b71ff fix comments 2026-09-02 07:28:25 +08:00
Halil Durak 87308b5e23 compute site-for-cookies from ancestor chain of a Frame
The site for cookies were computed from the immediate parent `Frame`, which would allow sending a cookie that's `SameSite=Strict` from 2 levels deep under. Directly from RFC6265bis, this PR essentially implements (except for step 4, we skip host-less ancestors):

   Given a Document (document), the following algorithm returns its
   "site for cookies":

   1.  Let top-document be the active document in document's navigable's
       top-level traversable.

   2.  Let top-origin be the origin of top-document's URI if top-
       document's sandboxed origin browsing context flag is set, and
       top-document's origin otherwise.

   3.  Let documents be a list consisting of the active documents of
       document's inclusive ancestor navigables.

   4.  For each item in documents:

       1.  Let origin be the origin of item's URI if item's sandboxed
           origin browsing context flag is set, and item's origin
           otherwise.

       2.  If origin is not same-site with top-origin, return an origin
           set to an opaque origin.

   5.  Return top-origin.
2026-09-02 07:19:35 +08:00
Karl Seguin 0b3e75ca08 Merge pull request #3361 from lightpanda-io/cdp-urlpattern
cdp: support request interception patterns
2026-09-02 07:13:02 +08:00
Adrià Arrufat 20ac0baa99 webapi: match browsers for step, time strings, empty patterns and tooLong
Checked the three test files in Firefox and Chrome:
- stepUp/stepDown use the HTML step base (min, else the value attribute)
  and snap off-ladder values to the next rung, counting the snap as the
  first step as browsers do; clamping lands on the last rung inside
  min/max.
- time strings keep a three-digit fraction.
- an empty pattern attribute is a pattern (matches only "").
- tooLong/tooShort only for values last changed by a user edit, so the
  text-entry path marks the value and script/attribute values never trip
  them; same for textarea.
- showPicker dropped: browsers throw NotAllowedError without a gesture,
  a no-op would be a lie.
- month/week assertions skipped where the browser has no such input.
2026-09-01 12:09:49 +02:00
Adrià Arrufat 6c59aa6df1 webapi: matchMedia change events and real (add|remove)Listener
addListener/removeListener were no-ops and nothing fired when
Emulation.setDeviceMetricsOverride crossed a breakpoint. Each
MediaQueryList now registers on its Page; Browser.setViewportOverride
tells pages to re-evaluate and dispatch MediaQueryListEvent('change') on
the ones whose matches flipped. onchange is registered as a listener so
JS-side dispatchEvent reaches it too.
2026-09-01 10:30:35 +02:00
Karl Seguin 39ac97f267 CDP failRequest also cancels (not aborts) 2026-09-01 15:50:48 +08:00
Karl Seguin 76c8f4bb6e webapi: window.stop()
window.stop() is less destructive than other mechanisms we have. For one, it
seems largely isolated to pending or inflight HTTP requests. For anther, it
keeps the page intact.

To achieve this, HttpClient gains an `cancelRequests` which is a gentler version
of `abortOwner`. It cancels inflight/pending HTTP requests, which results in
error callbacks (not shutdown callbacks) firing.

Just like https://github.com/lightpanda-io/browser/pull/3189 I ran into the
problem that I couldn't distinguish between an HTTP request that was canceled
because of user-action (e.g. calling window.stop(), or xhr.abort()) and an HTTP
request that was internally aborted. These now have distinct errors/flows so
that we can present the correct state. Most places that aborted now all
transfer.cancel() which results in a distinct `error.TransferCanceled` (some
places still abort -> `error.Abort`). It should be possible to revisit 3189 now.

The CDP "Page.stopLoading" now hooks into this new behavior. Fixes
https://github.com/lightpanda-io/browser/issues/3351
2026-09-01 14:55:19 +08:00
Karl Seguin 9c83bf3ef9 log: allow 'note' to be silence, log CLI tips
We currently have 1 note: it prints the server's listening address:port. Note
is a special un-ignorable level. This keeps the "note" level, but logs it under
a new scope: "note", so that it _can_ be silenced with a `--log-filter note`.

Add a new note, on startup, that displays tips. Currently, only displays when
--obey-robots is not enabled:

NOTE  note : config tips . . . . . . . . . . . . . . . . . . . [+0ms]
      robots = use '--obey-robots' to use a sites robots.txt
      meta = use '--log-filter note' to silence this message
2026-09-01 12:35:56 +08:00
Karl Seguin a68697921e cdp: remove race between terminate/disconnect
A client that disconnects might get treated as a harsher terminate failure (e.g.
watchdog). This doesn't have a huge impact, but it makes the CI flaky and it
produces more logs than is necessary.

In a terminate state, the driver will now check its inbox to see if this is a
client disconnection.
2026-09-01 12:11:07 +08:00
Karl Seguin b7d790561f cdp: support request interception patterns
The Fetch.enable call takes `patterns` which can limit the path and type of
request that should be intercepted. This adds the wildcard support.

As before, RI is currently only enabled for Requests, not response. A warning
is printed if RI for responses is requested.

Fixes: https://github.com/lightpanda-io/browser/issues/3349
2026-09-01 10:06:55 +08:00
Karl Seguin 731774ab67 ops: Allow serving both cdp and bidi on the same port
--protocol can now be specified multiple times. This just makes ops/dev easier
by only requiring 1 instance.
2026-08-31 22:45:15 +08:00
Karl Seguin 766c0d05d6 bidi: protocol selection CLI, start of [classic] WebDriver
In order to support Selenium the way people are used to, it looks  like we need
to support both WebDriver classic (WebDriver) and WebDriver BiDi (BiDi). Typical
scripts look like a mix of the two, e.g. using WebDriver to control the browser
and using BiDi  to receive notifications. This commit:

1 - adds a --protocol (cdp|webdriver) CLI argument to the `serve` command to
    enable one or the other protocol (defaulting to CDP)

2 - adds basic WebDriver endpoint to let a Selenium client connect. This
    implementation is hackish and sits on top of our simple Handshake handler.

The handshake handler is well past its original design. Serving /json/version
and /metrics from it was one thing. But Driving the entire browser session? This
will get a follow up PR.
2026-08-31 22:45:15 +08:00