Commit Graph
9542 Commits
Author SHA1 Message Date
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 f9eb4e437a cdp: wheel events scroll the viewport unless over a scroll container
Input.dispatchMouseEvent mouseWheel (and the BiDi wheel action, which
shares triggerMouseWheel) only ever moved the scroll position of the element
under the cursor, and gave up when no element was there. window.scrollY
reads a separate field that Window.scrollTo maintains, so a wheel never moved
the page and never fired the document's scroll event; every CDP driver's
scroll-by-wheel API was a no-op.

Scroll the nearest ancestor whose overflow is auto or scroll, as before, and
otherwise scroll the window through Window.scrollBy, which already schedules
the trusted scroll and scrollend events. A wheel over empty space targets the
root element instead of returning early.
2026-09-10 16:01:37 +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
Adrià Arrufat bebbbaf3d8 Probe own visibility inside walks that prune hidden subtrees
The AX writer probed the whole ancestor chain three times per node
(prune, ignored, childIds) and elementFromPoint once per node, although
neither ever descends into a hidden element, so below the walk root only
the element's own display can change the verdict. The AX walk now
prunes on the element's own attributes and cascade and hands writeNode
the verdict; the root and the query walk keep the chain probe.
elementFromPoint carries the verdict down its stack while still counting
hidden nodes, so positions keep agreeing with getBoundingClientRect.
2026-09-10 09:21:07 +02:00
Adrià Arrufat a142c42b60 Merge branch 'main' into inline-style-on-demand 2026-09-10 09:20:35 +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 c856f56289 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-10 10:34:44 +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 90bf686a70 Append inserted rules to the buckets instead of rebuilding
CSSStyleSheet.insertRule marked every sheet dirty, so the next visibility
query re-parsed every stylesheet. A style rule appended at or after the
sheet that emitted the last rule is last in cascade order: it now joins
the buckets with the next document order and only stamps the memo, and
an append that sets none of the tracked properties changes nothing at
all. Inserts in the middle, into an earlier sheet, or of at-rules still
take the rebuild path.

Unlayered rules carry UNLAYERED_RANK from creation, so an appended rule
packs exactly what a rebuild would. Materializing a <style> sheet's
cssRules no longer counts as a change, since the text and rule paths
now fold declarations the same way (!important included).
2026-09-09 22:49:37 +02: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
Halil Durak 380bdcfa00 change how Lax allowance computed (more RFC 2625bis compliance)
Specifically to distinguish cross-site iframe navigation from top-level navigation, this PR reworks how `SameSite=Lax` moved. Since we're not checking if its a navigation alone now, the field for it is also renamed to `lax_allowed`.
2026-09-09 19:43:41 +03: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 1bdb38770c add a matching navigation.currentEntry test 2026-09-09 07:58:11 -07:00
Muki Kiboigo f6e2210023 pushEntry instead of doing a full navigation initially 2026-09-09 07:57:21 -07:00
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
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
Adrià Arrufat a6bb66ac3c Merge pull request #3390 from lightpanda-io/ci/trigger-python-package
ci: release lightpanda-python on version tags
2026-09-09 14:48:02 +02:00
Adrià Arrufat 433ca9b747 adblock: fold the regex path into the existing mechanisms
The raw URL lives on `pattern.Url` next to the lowercased one, so
`pattern.matches` owns the `.regex` arm like every other kind and the
engine stops special-casing it. `Request.init` does the lowercasing
itself, as `fromHttp` already had to, instead of asking callers for
both spellings.

The regex shape now spells its uncertain marker as `*` and keeps
non-token literals as one marker, so it is read by the same
bounded-token loop as a plain pattern rather than a copy of it. The
quantifier parser keeps only what it uses: whether the atom may be
absent.

`Regex.matches` runs on a stack-first allocator: PCRE2 wants a match
data block and 20KB of backtracking frames per call, which no longer
touches the heap in the common case. A filter holds a pointer to its
regex, keeping `NetworkFilter` at its previous size.
2026-09-09 13:13:18 +02:00
Karl Seguin 719c2f967f Merge pull request #3458 from lightpanda-io/element-attribute-getters-ux
perf: improve common element attribute getters
2026-09-09 18:51:36 +08:00
Karl Seguin 54f1389ba9 Merge pull request #3457 from lightpanda-io/perf-attribute-lookup
perf: faster common attribute lookup + css selector class match
2026-09-09 18:51:19 +08:00
Adrià Arrufat 39974e461d Engine: index regex filters by the literals every match carries
A regex filter rode the fallback bucket, which every request pays for.
uBO reads a token out of one by flattening the pattern into a string
where literal characters stay and anything else becomes a marker that
says only whether a token character may be there, then taking the
alphanumeric runs bounded on both sides by something that is surely
not one; `tokenizableStrFromRegex`, ported here as `RegexShape`.

One departure: a positive lookaround becomes a marker rather than
being inlined, since a token must never come from text the regex does
not consume. Anything the flattening does not follow yields no token
at all, which is never wrong.

On the 79 regex rules the parser accepts across EasyList, EasyPrivacy
and uBO's lists, 72 now land in a bucket.
2026-09-09 12:49:15 +02:00
Adrià Arrufat c9329d5955 AdBlocker: run /regex/ filters with PCRE2
Filter lists carry a few hundred rules written as JavaScript regex
literals (24 in EasyList, 165 in uBO's badware list); they parsed but
were dropped as unsupported. PCRE2 reads that syntax as-is, `\/` and
friends included, its compiled patterns are immutable so the one
blocker shared by every HTTP client thread can run them, and 10.48
ships a build.zig for 0.16, so it is wired like sqlite3.

`Regex.Context` routes every PCRE2 allocation through the blocker's
allocator, which puts the compiled patterns under the test runner's
leak detection, and caps match and depth so a broken pattern costs a
false negative rather than a stalled request. As in uBO, a regex
tests the raw URL with the case-insensitive flag unless `$match-case`.

Regex filters are still never tokenized: they ride the fallback bucket.
2026-09-09 12:43:39 +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