Commit Graph
7431 Commits
Author SHA1 Message Date
Karl Seguin cbff8e8ce3 Merge pull request #3389 from lightpanda-io/better-redirect
http: handle redirects better
2026-09-03 19:37:44 +08:00
Karl Seguin cb26e05533 Merge pull request #3383 from lightpanda-io/intersection-runaway-guard
stability: disconnect runaway intersection observers
2026-09-03 08:50:09 +08:00
Karl Seguin d2ea2396f5 Merge pull request #3392 from staylor/fix/heap-exhaustion-teardown-3319
Avoid rearming heap termination during teardown
2026-09-03 08:27:19 +08:00
Karl Seguin bb9f9b36e9 split IO reset and disconnect
Allows reset to be called without disconnect, which allows the runnaway check
to more efficiently do both.
2026-09-03 07:47:16 +08:00
Karl Seguin 2a977351d7 centralize teardown protection (covers every case this way) 2026-09-03 07:28:55 +08:00
Karl Seguin 658d350404 Merge pull request #3329 from lightpanda-io/better-server
Better server
2026-09-03 07:27:33 +08:00
Karl Seguin 37f31afec5 Merge pull request #3387 from lightpanda-io/idb-unique-cursor
indexeddb: support nextunique and prevunique cursor directions
2026-09-03 07:23:06 +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
Pierre Tachoire 37a120cc77 event: keep keydown and worker error events alive past dispatch 2026-09-02 21:23:25 +02:00
Pierre Tachoire 3520065398 indexeddb: keep upgradeneeded event alive past dispatch 2026-09-02 21:13:06 +02:00
Scott Taylor 427960fd6c fix: avoid rearming heap termination during teardown
Assisted-By: devx/5efceed7-6cf0-4f8f-b1b4-5a1791ae8211
2026-09-02 09:23:11 -04:00
Adrià Arrufat 6b24f1e867 stability: bound intersection runaways by burst duration, not count
A delivery count cannot separate a runaway from a busy but healthy page:
airbnb legitimately needs up to ~50 delivery sessions per load, and the
storefront runaway had already exhausted the timer table by ~70.
Duration does: every healthy burst measured went quiet within 4.5s of
its first delivery, while the runaway never did.

Deliveries closer than 2s apart form a burst; a burst that runs past 10s
disconnects the frame's intersection observers. Measuring from the start
of the current burst rather than from the first delivery keeps lazy
loading alive on pages that live for minutes in serve or agent mode.

A chain that re-observes synchronously never leaves the microtask
checkpoint, so a per-burst ceiling of 1024 deliveries stops it from
spinning for the full limit.
2026-09-02 10:29:45 +02:00
Karl Seguin 91187e9483 Merge pull request #3381 from lightpanda-io/canvas-measure-text
webapi: canvas measureText and the drawing methods chart libraries probe
2026-09-02 15:15:41 +08:00
Karl Seguin 195e240c9d Add buffers to Context2D state to minimize allocations
RC TextMetrics

Share more state between CanvasRenderingContext2D and Offscreen
2026-09-02 14:51:15 +08:00
Karl Seguin 7d39a4fbaf http: handle redirects better
Our redirect handling was "optimized" to re-use the same easy connection, at the
cost of circumventing the entire pipeline. A redirect would not check the new
target's robots.txt, rate limit, CORS, Web Auth, ...

The new code simply restarts the pipeline (with a discriminator tag so that
things like CDP can tell the difference between a redirected request and a new
one).
2026-09-02 12:41:53 +08:00
Karl Seguin 235d10c6b5 Merge pull request #3373 from lightpanda-io/streams-queuing-strategies
webapi: CountQueuingStrategy and ByteLengthQueuingStrategy
2026-09-02 12:37:03 +08:00
Karl Seguin 10c6f553c8 fix copyright date, improve js parameter mapping 2026-09-02 11:00:22 +08:00
Adrià Arrufat a8ff1c6c91 webapi: CountQueuingStrategy and ByteLengthQueuingStrategy
Both globals were missing, so MDN-canonical stream setup code threw
ReferenceError. size() is a method rather than an accessor returning a
function; the streams don't consume it yet.
2026-09-02 11:00:22 +08:00
Karl Seguin c85208a3be zig fmt 2026-09-02 11:00:04 +08:00
Karl Seguin 1b50e235d5 Set max_connect based on config.httpMaxConcurrent()
This is per multi. So the original suggested 128 could result in 2048 idle
connection with 10 CDP connections.
2026-09-02 11:00:04 +08:00
Adrià Arrufat 39bd76e079 http: keep connections alive across navigations
CURLMOPT_MAXCONNECTS was never set, so libcurl used its default of 4x the
number of easy handles currently attached to the multi. Handles are added
and removed per transfer, so between page loads that default collapses to
roughly zero and every cached connection is evicted: revisiting a host
after browsing elsewhere re-paid connect + TLS every time.

Measured over 25 navigations across 5 sites (5 rounds, ReleaseFast), median
warm navigation drops from 0.73s to 0.42s on news.ycombinator.com and from
0.71s to 0.42s on github.com; total navigation time 11.1s -> 7.3s. Loading
one host repeatedly was already fast and is unchanged; the win is on
cross-site browsing, which is what agents and crawlers actually do.
2026-09-02 11:00:04 +08:00
Karl Seguin 2817922305 ci: disable watchdog during tests to reduce flake 2026-09-02 10:41:46 +08:00
Karl Seguin 8888be1229 indexeddb: support nextunique and prevunique cursor directions 2026-09-02 10:17:11 +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 eea50fe98a Merge pull request #3293 from lightpanda-io/nikneym/samesite-strict-bypass
compute site-for-cookies from ancestor chain of a `Frame`
2026-09-02 07:54:24 +08:00
Karl Seguin 5e42a253e1 Merge pull request #3372 from lightpanda-io/html-contenteditable-reflect
webapi: reflect HTMLElement.contentEditable
2026-09-02 07:29:40 +08:00
Karl Seguin 07198b71ff fix comments 2026-09-02 07:28:25 +08:00
Halil Durak add8a8c003 Cookie: getter/setter changes for site-for-cookies
Also updates matchCookies/onCookieChanged from the hard-coded "same-site + navigation" to areSameSite(exec.siteForCookies(), host) and is_navigation=false.
2026-09-02 07:19:39 +08:00
Halil Durak 9ddf6ad02a Worker: inherit site-for-cookies from the creating document 2026-09-02 07:19:39 +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 3befda2acc Merge pull request #3360 from lightpanda-io/indexeddb-better-errors
indexeddb: Improve error handling
2026-09-02 07:13:24 +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
Karl Seguin b852b7b89a Merge pull request #3380 from lightpanda-io/RenderTree
chore: Extract common Markdown/Screenshot logic
2026-09-02 07:12:29 +08:00
Adrià Arrufat afc97d31f3 stability: disconnect runaway intersection observers
We report every attached element as fully visible, because without layout the
alternative (a faux position vs. the viewport) hides everything past the first
screenful. Infinite scroll turns that into a non-terminating loop: the page
observes a sentinel, we report it intersecting immediately, the callback loads
the next page and observes a fresh sentinel, and so on with nothing to stop it.

Seen on a live storefront: the collection page paginated itself to page 10 over
68 delivery sessions and ~94 requests, exhausted the timer table
(error.TooManyTimeout), and hung until the 30s watchdog killed the navigation.
It reproduced on roughly one run in seven.

Cap the delivery sessions per document and disconnect the frame's intersection
observers past it, mirroring the MutationObserver runaway guard. The limit is
32: measured over a static page, an ad-heavy news hub and a storefront, no
document needed more than 7 sessions, while a runaway climbs without bound.

This bounds the damage rather than fixing the visibility model. Reporting
intersections against a real viewport, and resetting the budget when the page
actually scrolls, are the follow-ups.
2026-09-01 16:26:54 +02:00
Karl Seguin 4df0b5e5a3 Merge pull request #3374 from lightpanda-io/webapi-small-accessors
webapi: Headers.getSetCookie, customElements.getName, Element.part
2026-09-01 19:11:58 +08:00
Karl Seguin 41ae39789b zig fmt 2026-09-01 18:52:58 +08:00
Karl Seguin 96de22fc0a webapi: enhance header to block forbidden response headers 2026-09-01 18:51:39 +08:00
Karl Seguin d663afc0ee Merge pull request #3379 from lightpanda-io/input-value-as
webapi: input valueAsNumber, valueAsDate, stepUp/stepDown, showPicker
2026-09-01 18:43:32 +08:00
Karl Seguin 40fb080509 Merge pull request #3370 from lightpanda-io/svg-ctm
webapi: SVGGraphicsElement.getCTM and getScreenCTM
2026-09-01 18:13:54 +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
Karl Seguin ddb0dc09b9 Merge pull request #3377 from lightpanda-io/image-decode
webapi: img.decode()
2026-09-01 17:21:23 +08:00
Karl Seguin 9d2b3cf456 Merge pull request #3376 from lightpanda-io/disabled-fieldset-state
webapi: a disabled fieldset disables its controls' activation and validation
2026-09-01 17:21:12 +08:00
Karl Seguin 17dde356f4 add option and optgroup to controls we need to check disabled on 2026-09-01 17:05:55 +08:00
Karl Seguin 8e9ecb4a64 Merge pull request #3375 from lightpanda-io/option-selected-single
webapi: option.selected = true deselects its siblings in a single select
2026-09-01 16:56:46 +08:00