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.
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.
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.
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.
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.
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.
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.
focusEditingHostForMouseDown only walked contenteditable hosts, so a
div[tabindex=0] stayed unfocused after click. Replace it with
focusForMouseDown: still prefer the outermost editing host, then focus
the nearest mouse-focusable element (including tabindex=-1).
Call sites: actions.click, CDP triggerMousePress, WebDriver pointerDown.
Tests cover the MCP selector path, agent Page.click on the HTML fixture,
and a runtime-created child click (ancestor walk, non-focusable must not
steal focus, tabindex=-1 is mouse-focusable).
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
Round-1 review feedback on the pointerdown/mousedown/pointerup/mouseup/
click sequence added in 3e8ff142e:
- Guard disabled elements up front (el.isDisabled()), matching the checks
WebDriver.click and HtmlElement.click already have — otherwise a
disabled checkbox/button still activates.
- A cancelled pointerdown now suppresses both mousedown and mouseup for
the gesture (one check, not two independent ones — see below).
- mousedown now calls Frame.user_input.focusEditingHostForMouseDown,
matching the CDP path in frame/user_input.zig, so clicking a
contenteditable host or tabindex target actually focuses it.
- updateHoverTarget(..., .{ .with_pointer = true }) fires before the
first pointerdown, matching the hover step both other click paths take.
- click is now dispatched as a PointerEvent (pointerType "mouse",
pointerId 1), matching HTMLElement.click(), with pressure 0.5 while a
button is down.
- Dropped the always-0 `button` param from both dispatch helpers, merged
the duplicated dispatch/log code into one dispatch() helper, dropped
the restated clientX/clientY = 0 defaults, trimmed the doc comment.
Test coverage: the click assertion in tools.zig now checks the exact
five-event sequence (type:button:buttons:pointerType:isTrusted) instead
of a single boolean. Two new fixtures in mcp_actions.html close the gaps
the weaker test couldn't see: a button whose pointerdown listener calls
preventDefault() (asserts the sequence collapses to exactly pointerdown,
pointerup, click — no mousedown/mouseup), and a disabled button (asserts
mousedown never fires).
The first draft of the suppression fix wrongly gated mouseup on
pointerup's own preventDefault() instead of pointerdown's; the new
preventDefault fixture catches that regression too, confirmed by
reintroducing it and watching the assertion fail before restoring the
correct version.
Full suite (1339 tests) and zig fmt --check pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Eager tokenization is ~7% of a ~2.1 µs match. A lazy fill saved nothing because every request reaches the first engine. Streaming from scratch per engine cost +10% capped and +16% uncapped, and stayed +10% even with exception gating. The 128 cap changed 1 verdict in 242,908. So we went with hybrid approach: no token is lost, and 97% of URLs still pay one tokenization.
* `@@…$important` -> `error.InvalidOption`
* `||host`, `||host^`, `||host|` (and bare `host|`/`|host` lines) all read as "hostname or subdomain" and land in the trie when option-free.
* Wildcard trimming now follows uBO's pointless-wildcard rules.
* Engine.Request.fromHttp(req, source_url, buffers) now builds the adblock request straight from HttpClient.Request.
* The URL is tokenized once per request (hashed into the Request, shared by all engines); capped at 128 tokens (same as adblock-rust).
* Document hostname longer than 253 bytes now skips adblocking.
change --http-timeout default to 15000 (up from 5000)
change --http-connect-timeout default to 8000 (down from 300000, curl's default)
5 second _total_ transfer time can be a little tight. I generally don't see a
good reason to overly limit this value.
Worth noting that help.zon said the default for --http-timeout was 10000, but
it was, in fact, 5000.
Hopefully this improve situations like https://github.com/lightpanda-io/browser/issues/3395
which I believe are due to slow proxies.
Parsing HTML has different modes. Parsing HTML when setting innerHTML can be
different than parsing HTML inside a template, can be different than ...
html5ever handles this for us, provided we give it the correct context (the
tag name that the content is being parsed into). This adds an optional *Element
to the Parser which is fed into html5ever (its tagname).
Fixes: https://github.com/lightpanda-io/browser/issues/3336