Commit Graph
9369 Commits
Author SHA1 Message Date
Adrià Arrufat 0695e28585 browser: advance Date.now with virtual time
Each virtual time jump also moves the offset on V8's wall clock, through
the fork's v8__Platform__SetClockOffsetMillis, so Date.now() keeps pace
with the timers it drives. Pins zig-v8-fork at lightpanda-io/zig-v8-fork#203;
the prebuilt archive needs a tag bump once that lands.
2026-09-11 17:13:01 +08:00
Adrià Arrufat 5e1f872a32 browser: virtual time budget
Opt-in --virtual-time-budget-ms: when the page is idle except for pending
timers, jump the page clock to the next scheduler task instead of sleeping,
up to a per-navigation budget, then fall back to real time. Timers fire in
order and in-flight transfers are never skipped. Timers, performance.now,
event timestamps, resource timing, the network-idle hold and the
intersection burst guard read the page clock; the watchdog, HTTP client,
rate limiter and server keep the boot clock. Date.now stays real until the
V8 platform clock is offset too. Serve is unaffected.
2026-09-11 17:12:22 +08:00
Karl Seguin 9dc7b1bcb2 Merge pull request #3488 from lightpanda-io/isolate_shutdown_notification
v8: call v8__Platform__NotifyIsolateShutdown on isolate shutdown
2026-09-11 17:09:32 +08:00
Adrià Arrufat 28c1a69312 Merge pull request #3470 from lightpanda-io/incremental-insert-rule
Append inserted rules to the buckets instead of rebuilding
2026-09-11 17:09:19 +08:00
Adrià Arrufat a80e184142 Merge branch 'main' into incremental-insert-rule
main added custom property (`--*`) tracking to the same rule-building code
this branch made appendable. Both paths now share addSelectorRules, which
registers the custom declarations of a rule and its visibility rules, and
reports whether it added anything tracked. An insertRule append therefore
reaches custom_rules too, and clears the lazily parsed selectors of a
property it joins, instead of being dropped when the rule sets no
visibility property.

The raw-text path folds a block once for both kinds of declaration, so a
rebuild does not tokenize every block twice. rule_layers lives in the rule
arena so an appended rule can register its layer; the layered ranks are
still stamped by finalizeLayerRanks.
2026-09-11 08:51:31 +02:00
Karl Seguin 1cb568811e Merge pull request #3469 from lightpanda-io/inline-style-on-demand
Materialize inline styles where layout reads them
2026-09-11 14:07:23 +08:00
Karl Seguin 1e128bbb16 Merge pull request #3487 from lightpanda-io/http_response_dupe_optimization
mem: reduce memory usage of cloned HTTP responses
2026-09-11 14:07:07 +08:00
Karl Seguin 9a44e17ffd Merge pull request #3481 from lightpanda-io/cdp-honor-params
cdp: honor runImmediately, screenWidth/screenHeight and browserContextId
2026-09-11 14:06:58 +08:00
Karl Seguin f81f6e4eb7 mem: reduce memory usage of cloned HTTP responses
ScriptManager, XMLHttpRequest.zig, Fetch, Workers, etc. all take ownership (aka
dupe) the HTTP response from HTTPClient. They all have a headerCallback that
does something like:

```zig
if (transfer.getContentLength()) |cl| {
  try self.body.ensureTotalCapacity(self.arena, cl);
}
```

But in all non-streaming cases (which is most cases),  the HttpClient buffers
the response and only calls the headerCallback _after_ the body has been
received. Rather than relying on "Content-Length" header, the body buffer can
be sized to the exact body length. Why does this matter? Because the
Content-Length is the length of the body on the wire, and if the body is
compressed (like almost all .js files are), it will under-report the final
body length AND, because most callers are using an arena, the buffer growth
will retain more memory than it should.

This adds a `transfer.bodyLen()` method. Callers which dupe the body now use
this rather than the Content-Length (Content-Length is still used, e.g. for
XHR progress report).
2026-09-11 11:50:30 +08:00
Adrià Arrufat 7b7b7aa9f3 Materialize inline styles where layout reads them
Visibility probes no longer decide whether an element's style attribute
gets parsed into a CSSStyleProperties object. The three layout readers
(getElementAxis, positionStyle, horizontalPosition) create it on demand
through Element.inlineStyle, and StyleManager only ever folds the
attribute text. That removes the scan/materialize mode threaded through
every probe, and a JS layout read now only materializes the elements
whose inline style it actually reads.
2026-09-11 10:43:31 +08:00
Karl Seguin 65f8a83585 v8: call v8__Platform__NotifyIsolateShutdown on isolate shutdown
The v8 documentation says this "Has to be called", and claude flagged it as a
potential leak.

Required a new zig-v8-fork build (v0.5.5) which I released.
2026-09-11 10:20:20 +08:00
Karl Seguin 241083a85c Merge pull request #3428 from lightpanda-io/strip-shell
render: add "shell" and "clutter" option to --strip-mode
2026-09-11 08:54:53 +08:00
Karl Seguin 95e3900f3b orderfile 2026-09-11 08:37:53 +08:00
Karl Seguin faee5a1595 Merge pull request #3486 from lightpanda-io/observer-leak
mem: Fix leak when JS is terminated during MO/IO delivery
2026-09-11 08:24:39 +08:00
Karl Seguin e62ef0c6ca Raise VmHWM regression from 24MB -> 25MB
This doesn't have anything to do specifically with this PR, but I've been
fighting that limit for a number of PRs over the last couple days.
2026-09-11 06:40:54 +08:00
Karl Seguin 218a4bacd4 split out protocol.json to 1 line per command 2026-09-11 06:40:23 +08:00
Karl Seguin e8f6591411 address feedback
remove --strip-mode full   (and all `full` support from cli, because it makes
backwards-compatibility a pain)
2026-09-11 06:36:49 +08:00
Karl Seguin 5bc4f7482c render: add "clutter" option to --strip-mode
Adds a `clutter` option to --strip-mode. This is based on readability.js. It
isn't a direct port (e.g. it doesn't strop bylines). It fallsback to `shell` if
it strips too much (and shell itself can fallback to not stripping anything).
But clutter rarely fallback to shell, only when a page is very small or when
it strips out _a lot_.

Also expanded shell to look at class names and ids.
2026-09-11 06:36:48 +08:00
Karl Seguin dae5e4a623 mem: Fix leak when JS is terminated during MO/IO delivery
Both MO and IO assumed an orderly transfer of its pending records to v8. But,
failure to transfer these objects to v8 (e.g. if the terminate flag was set)
means the records and their .tiny arenas were lost forever.
2026-09-11 06:36:10 +08:00
Karl Seguin cdceca37c3 render: add "shell" option to --strip-mode
Some additional API changes:
- Add strip-mode support to pdf/png generation.
- Add LP.dump which provides greater content gathering capability to CDP,
  exposing most `fetch` dump-related parameters (e.g. format, strip, selector,
  ...)

The new "--strip-mode shell" is designed to try to remove non-content elements
such as the header and footer. The end goal is to use readibility.js test cases
as a baseline, but this isn't a port of readibility.js.

This is just the basic implementation of this, e.g removing a few key tags, e.g.
<header>, <footer> and considering some specific roles.

Even if --strip-mode shell is used, we might decide to stick with a whole dump:
it's better to strip not enough than to strip too much. This currently works by
measuring the ratio of non-link text of the stripped vs unstripped page.
2026-09-11 06:35:26 +08:00
Karl Seguin c21462cc38 Merge pull request #3480 from lightpanda-io/cdp-dom-focus
cdp: add DOM.focus
2026-09-11 06:29:34 +08:00
Karl Seguin 0837292d5e Merge pull request #3465 from lightpanda-io/getCurrentEntry-crash
Fix `history.state` and `Navigation.currentEntry` crash
2026-09-11 06:24:25 +08:00
Karl Seguin ceaa81721b update protocol.json 2026-09-11 06:09:51 +08:00
Karl Seguin 35e13e4d64 dom.focus only on focusable elements 2026-09-11 06:02:27 +08:00
Karl Seguin 561d4ab0ea Merge pull request #3482 from lightpanda-io/deps-releasefast
build: compile dependencies in ReleaseFast regardless of -Doptimize
2026-09-11 05:55:02 +08:00
Karl Seguin 33c9f3b9fd Merge pull request #3401 from lightpanda-io/http-webdriver
webdriver: HTTP WebDriver session management
2026-09-11 05:41:33 +08:00
Karl Seguin 9e44738775 update orderfile 2026-09-11 05:40:19 +08:00
Adrià Arrufat 585cfb06dd build: compile dependencies in ReleaseFast regardless of -Doptimize
Debug and release builds each compiled their own copy of every C
dependency and of the Rust staticlib, because build.zig threaded the
top-level optimize mode into all of them. Under dev_fast the deps also
picked up the bundled-CRT target query, so even the same mode could not
share objects with a plain build.

Dependencies now build in ReleaseFast for the requested target, the way
the prebuilt V8 archive already works. Debug and release builds share
one set of cached dependency objects, and debug binaries run TLS, HTML
parsing, regex and sqlite optimized. -Ddebug_deps restores the old
behaviour for stepping into a dependency.

The Rust staticlib can only be shared by dropping the Debug-only memstats
feature: its single export, html5ever_get_memory_usage, was declared on
the Zig side but never called, and it pulled a jemalloc build into every
cold debug build. The Makefile override that existed for jemalloc's
nested make goes with it.
2026-09-11 05:40:17 +08:00
Karl Seguin 2ace9d1548 make socket ownership more explicit and future-proof 2026-09-11 05:24:12 +08:00
Karl Seguin bcf69ced9c address feedback
tighten socket ownership (on error paths)

allow reaper to be disabled

Handle window where link is being destroyed, worker is still alive, and client
attempts to re-link.
2026-09-11 05:11:36 +08:00
Karl Seguin 3fb4539b87 fix import 2026-09-11 05:11:36 +08:00
Karl Seguin 6392454892 fix test-only tsan issue 2026-09-11 05:11:36 +08:00
Karl Seguin 8ad9eaf48d webdriver: HTTP WebDriver session management
This is a small step towards WebDriver supports (non-bidi). It allows creating
and deleting a BiDi "Session" (e.g. a worker). It also allows attaching a BiDi
driver to an HTTP-created BiDi session (the typical selenium startup flow).

This change unblocks the most basic setup/teardown of Selenium, so it still
isn't enough to actually use a Selenium script as-is. But it's significant
because it models a worker (thread) that isn't tied to a WebSocket, something we
haven't had before.

A consequence of a pure HTTP Session is that we don't have a clear cleanup
signal. There is no "the socket is disconnected". There's a new HTTP reaper
which kills HTTP Sessions after --http-session-timeout. It's expected that
drivers properly DELETE /session/:id. I imagine we're going to run into
--cdp-max-connections limits and need to tweak this code. BUT, this entire flow
is only enabled with --protocol webdriver, so it won't impact exiting CDP users.
2026-09-11 05:11:26 +08:00
Karl Seguin 72165ef2a4 Merge pull request #3476 from lightpanda-io/make-private-if-private
chore: make declarations private if they don't need to be public
2026-09-11 05:09:17 +08:00
Muki Kiboigo 676fa59835 update ids in CDP navigation tests 2026-09-10 13:16:40 -07:00
Muki Kiboigo 87e7403826 move initial page creation to createPage 2026-09-10 13:15:20 -07:00
Adrià Arrufat 94381d4d71 cdp: honor runImmediately, screenWidth/screenHeight and browserContextId
Three parameters every Playwright and Stagehand session sends were parsed
and then only logged as not implemented:

- Page.addScriptToEvaluateOnNewDocument runImmediately now also evaluates
  the script in the current document, in the requested world.
- Emulation.setDeviceMetricsOverride screenWidth/screenHeight now back
  window.screen, kept on the viewport override next to width/height; 0
  keeps the current value, as for the other dimensions.
- Browser.setDownloadBehavior browserContextId is checked against the
  loaded context, as the Storage commands already do.
2026-09-10 16:07:26 +02:00
Adrià Arrufat 9f4f31820f cdp: add DOM.focus
Drivers focus a node before typing (chromedp's SendKeys calls DOM.focus,
then Input.dispatchKeyEvent). The method was unknown, so the keystrokes
went to the previously active element and every chromedp form fill was a
no-op. Resolve the node like the other DOM commands and call Element.focus,
which already handles focusability and the blur/focus event sequence.
2026-09-10 16:04:21 +02:00
Adrià Arrufat d693f49872 Merge pull request #3463 from lightpanda-io/adblock-regex-pcre2
`AdBlocker`: run /regex/ filters with PCRE2
2026-09-10 14:04:14 +02:00
Adrià Arrufat aecb115771 Regex: a failed compile allocation is PCRE2_ERROR_HEAP_FAILED
Compile errors are reported as 100 plus the internal number, and the
one for a failed allocation has a public name.
2026-09-10 11:25:20 +02:00
Adrià Arrufat 06f7f6f295 Engine: look through an optional stretch when reading a branch's ends
An alternation or a repeat keeps only whether its text may start and
end with a token character, and read that off one marker. An optional
non-token stretch there (`\/?x`) was taken as a definite non-token,
so `\/ads(\/?x|\/y)` was filed under "ads" while `/adsx` carries no
such token. What follows the stretch answers now.
2026-09-10 11:25:20 +02:00
Karl Seguin 2e6999f20b chore: make declarations private if they don't need to be public
This change is 99%  s/pub//   + a handful of dead code removal.
2026-09-10 14:42:09 +08:00
Karl Seguin b26d487b2a Merge pull request #3461 from lightpanda-io/style-manager-custom-properties
webapi: add basic support for custom properties
2026-09-10 12:37:49 +08:00
Karl Seguin e83e1e76ea webapi: add basic support for custom properties
Attempt to improve https://github.com/lightpanda-io/browser/issues/3199

Meant an leaner alternative to https://github.com/lightpanda-io/browser/pull/3440

3440 is close, but it adds 16 bytes to every VisibilityRule which isn't ideal
especially since the majority of these rules have no custom properties.

Let's make a few reasonable assumptions:
1 - Most custom properties don't have many distinct selectors
    (--background-color might be defined on, :root, and .card and a few others)

2 - Most custom properties aren't queried from JS (Tailwind can define thousands
    of custom properties, but they're used by the rendering engine, not from JS)

3 - Most selectors don't have custom properties

The design is to inverse what we do for visibility: lookup per property. We end
up with a property-name -> [(selector, value), (selector, value)] lookup. If
there's a query for --foreground, we O(1) to get the list of (selector, value)
and then iterate through this (hopefully) short list to get the value.

PLUS, we only parse the selector on the first query. So for those sites with
thousands of custom-properties, we're not wastefully storing / parsing the
selector on every build.
2026-09-10 12:09:50 +08:00
Karl Seguin dd0fd04267 Merge pull request #3475 from lightpanda-io/style-visibility-memo
Style visibility memo
2026-09-10 12:09:24 +08:00
Adrià Arrufat 9d994efbee Merge pull request #3468 from lightpanda-io/drop-visibility-call-caches
Drop the per-call visibility caches
2026-09-10 11:20:32 +08:00
Karl Seguin 3b4088ab62 use node's own frame, not the callers 2026-09-10 11:03:46 +08:00
Adrià Arrufat 60139f395b Merge pull request #3467 from lightpanda-io/style-visibility-memo
perf: memoize StyleManager visibility per element
2026-09-10 11:02:19 +08:00
Adrià Arrufat 1583a4b45f Drop the per-call visibility caches
With the StyleManager memo, a VisibilityCache/PointerEventsCache hit
costs the same hash probe as a memo hit, so the caches only added a
second probe per ancestor, a call_arena allocation per element, and
plumbing through SemanticTree, AXNode, CDP, links, ResizeObserver and
elementFromPoint. checkVisibilityCached becomes isVisible.
2026-09-10 10:34:43 +08:00
Karl Seguin 482d63e89e update style_version change on:
1 - history change (:target)
2 - document._active_element change (various places, added doc.setActiveElement())
3 - fragment-only navigation (:target)
4 - checkbox/radio commit/rollback (:checked)
2026-09-10 10:33:45 +08:00