Commit Graph
7585 Commits
Author SHA1 Message Date
Pierre Tachoire 79b4604f2b apply nav burst to the first requests only and set it to 10 by default 2026-09-10 08:17:02 +02:00
Pierre Tachoire ce0474d81f adjust cooldown intervals to land on 10 req/s 2026-09-10 08:17:01 +02:00
Pierre Tachoire d846042e5a catch error when observing the rate limit 2026-09-10 08:17:01 +02:00
Pierre Tachoire 1ac49bc8ae network: per-host pressure in the navigation rate limiter
The interval between two top-level navigations to the same host now
grows with the number of navigations sent to it: one extra base interval
every burst*10 navigations, capped at 60x. Pressure cools down by one
unit for every 10 base intervals the host is left alone.
--http-nav-delay is now a minimum, not a fixed spacing.
2026-09-10 08:16:53 +02: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
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
Karl Seguin 33ddbdf0fc Merge pull request #3466 from lightpanda-io/locale-timezone
cli: add --locale and --timezone, derive language signals from one value
2026-09-10 08:06:39 +08:00
Karl Seguin 471ea3bd80 Merge pull request #3464 from lightpanda-io/fix-attr-clone-owner-element
dom: detach cloned Attr from its owner element
2026-09-10 08:01:41 +08:00
Karl Seguin 58e1a547d8 fix help ordering, remove unnecessary pub 2026-09-10 07:41:34 +08:00
Karl Seguin 0cca01c0e8 Register a detached node's frame
Clear attribute element on remove

Relax naming validation to match Firefox/Chrome
2026-09-10 07:34:59 +08:00
Karl Seguin 1a7904843d Merge pull request #3460 from lightpanda-io/http-headers
webapi: Origin header + request header validation
2026-09-10 06:45:50 +08:00
Karl Seguin befea97d1e Merge pull request #3459 from lightpanda-io/named-interceptor-query
webapi: improve indexing / enumerable of various collections
2026-09-10 06:45:37 +08:00
Karl Seguin 773b7cbb22 Merge pull request #3455 from lightpanda-io/fix-cdp-notification-crash
Fix `Notification` use-after-free
2026-09-10 06:32:26 +08:00
Adrià Arrufat fec94aae0a Memoize StyleManager visibility per element
Every ancestor-aware visibility query (checkVisibility, offsetWidth,
getBoundingClientRect, getComputedStyle().visibility, pointer-events, and
the Zig tree walkers) re-resolved each ancestor from scratch. Resolve an
element's display/visibility/opacity/pointer-events once and reuse it
while Page.style_version is unchanged.

The inline style attribute is scanned once for all four properties, the
pointer-events scan shares the same path, and a materialize-mode hit still
creates the inline style object that layout reads.
2026-09-09 22:47:36 +02:00
Adrià Arrufat 84c46b6331 Add Page.style_version stamp for style-dependent state
Bumped through Frame.styleChanged: with dom_version, and by the state
changes that alter a selector match without changing what live
collections see: checked/indeterminate, input values, custom element
definitions, character data (:empty), parser insertions and stylesheet
changes. Stamps the visibility memo added next.
2026-09-09 20:46:06 +02:00
Adrià Arrufat 37bf7b68c1 cli: one Accept-Language parser for the header and navigator.languages
navigator.languages now lists the Accept-Language tags in order, which is
Chrome's contract, instead of a second derivation from the locale tag that
disagreed with the header (--locale de-DE sent de-DE,de,en but reported
["de-DE","de"]). HttpHeaders.AcceptLanguage owns both shapes and is also
the CDP override type.

ICU canonicalizes a BCP 47 tag read from LC_ALL itself, script subtag
included, so the POSIX id conversion is gone; it dropped the script and
turned zh-Hans-TW into Traditional Chinese.

Also: the CDP handler keeps validateUserAgent's verdict instead of scanning
for Mozilla twice, the override is cleared unconditionally on context
teardown instead of through a flag, and the flags are sentinel strings so
Platform passes them to setenv without copying.
2026-09-09 18:07:03 +02:00
Adrià Arrufat c55afc1df9 cli: add --locale and --timezone, derive language signals from one value
navigator.language was hard-coded to en-US and Accept-Language was a
constant, while Intl, toLocaleString and Date followed the host process
environment. On a de_DE host a page saw navigator.language === "en-US"
next to German number formatting, a mismatch fingerprinting scripts look
for, and the same page rendered differently across machines.

Follow Chrome's --lang rule: one configured tag drives navigator.language(s),
the Accept-Language header and ICU's default locale. --locale defaults to
en-US, so Intl is now en-US on every host instead of whatever LANG says.
--timezone sets the IANA zone Date and Intl use; absent, the host zone stays.

Both are applied by writing LC_ALL and TZ before V8 initializes ICU, which
reads them lazily. Platform.init is the first call in App.init, before any
thread exists, so setenv is safe there.

CDP Emulation.setUserAgentOverride.acceptLanguage, which Playwright sends
for its locale option, now overrides the header and navigator.languages
for the browser context's lifetime, mirroring the user agent override, and
applies even when the Mozilla user agent is refused.

Emulation.setLocaleOverride and setTimezoneOverride stay no-ops: changing
ICU's defaults at runtime needs new zig-v8-fork bindings.
2026-09-09 17:49:02 +02:00
Muki Kiboigo 260ad4f8db move notification fix to Session.deinit 2026-09-09 07:37:11 -07:00
Adrià Arrufat 51974bb346 dom: detach cloned Attr from its owner element
Attr.cloneNode() copied _element, so the clone still claimed the original
owner. Element.setAttributeNode() then tried to remove it from that owner,
which never held it, and threw NotFoundError. Per the DOM spec a cloned Attr
has a null ownerElement.

While there, make setAttributeNode() throw InUseAttributeError when the Attr
belongs to another element instead of silently moving it, matching the spec
and both Chrome and Firefox.

Mozilla's Readability.js hits this on every page (_setNodeTag,
_simplifyNestedElements), as does any code that copies attributes with
el.setAttributeNode(attr.cloneNode()).
2026-09-09 16:21:50 +02:00
Karl Seguin 66af1dc58d webapi: Origin header + request header validation
Driven by a handful of /fetch/ WPT tests, three changes:

1 - Prevent libcurl from auto-inserting a 'application/x-www-form-urlencoded"
    content type for types we really have no content-type for.

2 - Include origin header in all requests that should have it. This is something
    CorsGate was doing in most cases, but cors can be disabled, so the logic
    is now moved to HttpClient.

3 - Expands on the header guard added in  https://github.com/lightpanda-io/browser/pull/3374/
    Adds more modes and more header check. Request.init also uses the header
    guard now
2026-09-09 14:44:48 +08:00
Karl Seguin 5b2279ba16 webapi: improve indexing / enumerable of various collections
Inspired by https://github.com/lightpanda-io/browser/pull/3454 which added
the presence check for DOMStringMap, this adds the check to a number of other
collections (PluginArray, HTMLAllCollection, ...).

It also adds custom element reactions on indexed properties (previously only
existing for named index properties).

Finally, DOMStringMap now correctly handle numeric (or number-looking) indexes.
2026-09-09 11:39:15 +08:00
Karl Seguin a9af75034d perf: improve common element attribute getters
In main, there's a `getId`, and `getClassName` (etc...) getter on Element. But
these all `orelse ""`, because that's what the WebAPI wants. Internally though,
most code want the optional. The result is that _many_ places do:

```zig
el.getAttributeSafe(comptime .wrap("id"))
```

instead of:

```zig
el.getId()
```

This is a bit tedious AND, it means that when we improve `Element.getId` (1) no
internal caller benefits from it. This commit makes the element getters return
the optional (`?[]const u8`) and updates every callsite to use the new getter.
The `orelse ""` needed by the WebAPI is moved to the JsApi bridge.

(1) https://github.com/lightpanda-io/browser/pull/3457
2026-09-09 11:22:37 +08:00
Karl Seguin 5a55185ffe perf: faster common attribute lookup + css selector class match
Follow up to https://github.com/lightpanda-io/browser/pull/3449 which improved
nth-* type CSS selectors, this improves common attribute lookup and CSS selector
class matching.

Two distinct changes:
1 - We already have a `getAttributeSafe` for string literals. Now we have a
`getAttributeInterned` for known interned value (compile error when used with
a non-interned string, so safe). This relies on attribute names always going
through String.intern (which they do). Turns an equality check into a single
pointer comparison

2 - Improve how an element's class list is matches against a specific class.
Rather than tokenize + compare, this looks for the first character of the class
then does a boundary check.

In no case is this slower, but the gain depends on a number of factors,
including how many attributes an element has, and how many classes a class list
has. But a class selector goes from ~60us/query to ~40us/query compared to
Firefox's 47us/query.
2026-09-09 10:34:28 +08:00
Karl Seguin 6efc35cfe1 Merge pull request #3454 from staylor/staylor-3319-dataset-hasown
Fix dataset property presence checks
2026-09-09 08:33:25 +08:00
Karl Seguin 0b5eef9515 Merge pull request #3451 from lightpanda-io/cli-did-you-mean
cli: suggest the closest flag or command on a typo
2026-09-09 07:15:37 +08:00
Karl Seguin b3dd5f7de1 Merge pull request #3456 from lightpanda-io/fix-v8-reentrancy-in-window
Fix V8 Reentry on unhandled promise
2026-09-09 07:11:40 +08:00
Karl Seguin 6fce5ac1f1 Merge pull request #3449 from lightpanda-io/css-selector-nth-cache
perf: improve nth-* CSS selector performance
2026-09-09 06:53:30 +08:00
Karl Seguin 5896eeb260 Merge pull request #3447 from lightpanda-io/global-scope
chore: refactor Execution and GlobalScope
2026-09-09 06:53:10 +08:00
Karl Seguin 91f596eba3 Merge pull request #3442 from lightpanda-io/wpt-fetch
webapi: improve various WPT fetch apis
2026-09-09 06:52:44 +08:00
Karl Seguin 816d9daa98 Merge pull request #3446 from lightpanda-io/performance-eventtarget
webapi: Performance is an EventTarget
2026-09-09 06:52:11 +08:00
Muki Kiboigo 6de56aedae wrap debugValue in try catch 2026-09-08 13:55:54 -07:00
Muki Kiboigo 3f6b0d2d4a fix notification use-after-free 2026-09-08 11:54:34 -07:00
Scott Taylor 37850aa84e fix: report dataset named property presence via query interceptor
Without a query callback on DOMStringMap's named property interceptor,
'name' in element.dataset and Object.hasOwn(element.dataset, name)
returned true for every name, because the getter intercepts absent
keys as undefined. Pages that feature-detect data-* attributes in
custom element constructors took the wrong branch; one observed
pattern (a constructor that reparents itself when a data attribute is
present) turned cloneNode(true) into an unbounded loop over a growing
live child list, ending in a watchdog termination.

Add a query callback that maps the camel-case name to its data-*
attribute and reports actual presence, matching spec and other
engines.
2026-09-08 14:31:06 -04:00
Adrià Arrufat a32d30fa65 cli: suggest the closest enum value on a typo
A misspelt value for an enum-typed flag now names the nearest tag as
did_you_mean. --dump's peeking validator treats a word within two
edits of a format name as that mistake rather than as the url, so
--dump htmx no longer becomes a second url.

tagNames moves from Config into cli so both can use it.
2026-09-08 17:33:48 +02:00
Adrià Arrufat 88d133a676 cli: suggest the closest flag or command on a typo
An unknown --flag now logs the nearest accepted spelling within two
edits as did_you_mean, painted green next to the red typo in the pretty
log format; logfmt stays plain. A bare first argument within two edits
of a command name is rejected with the same hint instead of being
fetched as a url.

The Levenshtein helper moves from SlashCommand into string.zig so the
agent REPL and the CLI share it, with the table widened to fit the
longest flag name.
2026-09-08 16:50:46 +02:00
Karl Seguin 1105aec02b perf: improve nth-* CSS selector performance
This adds an NthCache which will cache the ordinal position of elements. Only
nodes which have >= 32 sibblings are cached - the cache has performance/memory
overhead and for smaller nodes, walking is cheaper.

Using the new benchmark: https://github.com/lightpanda-io/demo/pull/245 under
particularly bad conditions (W=4000):

main:
  case                      match   iters    us/query
  :nth-child(2n)             2000       2     55862.5
  :nth-of-type(2n)           1999       2     57675.0
  :nth-last-of-type(2n)      1999       2     53935.0
  ancestor :nth-child(2n)    4000       1    111855.0
  list of two nth            2667       1    111180.0

PR:
  case                      match   iters    us/query
  :nth-child(2n)             2000     258       388.1
  :nth-of-type(2n)           1999     245       409.6
  :nth-last-of-type(2n)      1999     245       409.0
  ancestor :nth-child(2n)    4000     168       596.1
  list of two nth            2667     160       626.8

Firefox
  case                      match   iters    us/query
  :nth-child(2n)             2000     460       217.4
  :nth-of-type(2n)           1999     445       224.7
  :nth-last-of-type(2n)      1999     260       384.6
  ancestor :nth-child(2n)    4000     246       406.5
  list of two nth            2667     294       340.1

With no regressions in the other cases.
2026-09-08 20:43:30 +08:00
Karl Seguin 3061930153 chore: refactor Execution and GlobalScope
The js.Execution is the API behind the Frame/WGS split, but the split is
actually held by the underlying js.GlobalScope. Most Execution methods are:

```zig
    return switch (self.js.global) {
        inline else => |g| g.isSameOrigin(url),
    };
```

And that works well, except that in some cases, code has a js.Context, not an
js.Execution, and they need to do the same inline switch.

This commit moves GlobalScope from src/browser/js to src/browser (there's
nothing JS/v8 about it), and moves all those inline switches into it. The
js.Execution API stays the same (it forwards the call to js.global)
but all callers that directly inlined switched the js.global no longer do.
2026-09-08 17:46:11 +08:00
Karl Seguin 1c9ec1b2c7 webapi: Performance is an EventTarget
Raises "resourcetimingbufferfull" when full.
2026-09-08 17:18:52 +08:00
Karl Seguin 5d732eb8f1 Merge pull request #3445 from lightpanda-io/timer-caps
stability: raise timer caps
2026-09-08 16:27:37 +08:00
Adrià Arrufat 328af59215 stability: raise timer caps
A paginated storefront listing holds ~2.7k pending timers, which crosses
the 2048 table cap. Past it setTimeout throws TooManyTimeout into the
framework's scheduler, which turns a busy page into a retry storm. No
browser throws there: raise the combined cap to 8192 and repeating timers
to 2048 so it stays a runaway backstop, not a budget.
2026-09-08 09:56:34 +02: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 b633dc4b16 webapi: improve various WPT fetch apis
Headers strip whitespace and guard against invalid characters

Headers iterator sorts and combines PER step, so that mutations are picked up.
Not the most efficient, but this is a short list, and how often are these being
iterated?

XMLHttpRequest: has its own extra header validation

Mime support for multiple Content-Type headers (or a header with multiple values)
last value wins.

Add BufferSource js bridge type that accepts various types -> []const u8 (at the
cost of losing the actual type). Useful in fetch, where various types can be a
body, but we only care about the underlying bytes (e.g. we didn't support A
rrayBufferView before this)

Refactored response body getters so that they all go through the same consume
and resolve logic
2026-09-08 13:12:20 +08:00
Karl Seguin 0f0abf02ea Merge pull request #3409 from lightpanda-io/contextless-custom-elements
webapi: don't upgrade custom elements in a windowless document
2026-09-07 17:46:11 +08:00
Karl Seguin 92d5f8f7ad Merge pull request #3412 from lightpanda-io/worker-cancelled-not-error
webapi: don't fire error event when a worker script fetch is aborted
2026-09-07 17:43:30 +08:00
Karl Seguin 3387310f6e Merge pull request #3426 from lightpanda-io/micro-pseudoclass-parse
perf: Micro-optimize pseudoclass parsing
2026-09-07 17:42:42 +08:00
Karl Seguin 82344c609f Merge pull request #3432 from lightpanda-io/tweak-http-timeouts
http: tweak http timeouts
2026-09-07 17:41:46 +08:00