CI build, for e2e-test and nightly now build with:
-Dorderfile/lightpanda.ld
This file informs the build on how to organize the code in the binary, grouping
hot code together so that we have to load less of the binary into memory.
lightpanda.ld will drift: we'll refactor our code, add new features, update
dependencies, update Zig, ... So it has to be re-generated. But we can do that
automatically in the CI (say, before the nightly build). That's for a follow up
PR.
This does not currently cover V8. V8 is being build with
`-no-unique-section-names`, so we don't get names that we can correctly
organize. The real win comes from doing this in V8, since a lot of V8 is cold.
This PR can land as-is, a zig-v8-fork PR will remove that flag, and then we can
have a follow up PR with an lightpanda.ld that includes the v8 symbols.
This is opt-in (via the -Dorderfile flag) because it adds ~20 seconds of
linking time.
CSS keywords are ASCII case-insensitive, but StyleManager matched
display:none / visibility:hidden|collapse / opacity:0 / pointer-events:none
with exact String.eql, so `style="DISPLAY: NONE"` or a `.x { Visibility:
Hidden }` rule left the element visible to checkVisibility,
getComputedStyle, the semantic tree and interactiveElements.
Compare at the check sites via a new String.eqlSliceIgnoreCase rather than
lowercasing declared values, which would corrupt content, url() and custom
property values that el.style must reflect verbatim.
std.http.Server's respond() only drains the request body when the server
keeps the connection alive; with keep_alive=false it leaves the body
unread, sets reader.state = .closing, and expects the caller to sever the
connection ("the connection will be severed after the response is sent").
handleConn looped on the client's keep_alive flag instead, which is true
for any HTTP/1.1 request, so after a 405/413/415/417/403 it called
receiveHead() again and fed the unread POST body to the head parser. The
server advertised `connection: close` but held the socket open until the
peer gave up.
Honour the reader's closing state so the socket is closed right after the
rejection response.
`engineKey` (on `lp.environ().getPosix`) replaces the env-var wrapper and
the three hand-spelled set/keyless/missing decisions in the cascade, the
explicit path, and the REPL query. `.auto`'s "always a rung" invariant is
now a comptime assert instead of an unreachable sentinel. HTTP retries are
disabled in `apiSearch` for every path — the cascade or the model is the
retry, and the tool's `timeout` argument bounds one attempt — rather than
threaded through as a parameter the table no longer set. The cascade
sentence is one shared constant for the tool description and the REPL
help, and search gets its own default timeout instead of borrowing the
navigation one.
The DuckDuckGo HTML endpoint the search tool fell back to is disallowed by
its robots.txt, so it is removed: the `duckduckgo` engine, the scrape via
`performGoto`, and the tool's browser-side dependency (`execSearch` no
longer needs a session). In its place `.auto` is a single walk over the
API engines in table order — each when its key is set — with Keenable
last, keyed if `KEENABLE_API_KEY` is set and its public endpoint
otherwise. Search therefore always has a rung with zero configuration,
and a set key is never shadowed by a keyless retry.
Every search now goes through zenai's `std.http.Client`, which had no
timeout at all; a stalled response would have blocked the MCP server's
shared browser thread for every session. zenai now bounds each attempt
(lightpanda-io/zenai#10, pinned here), and the tool's `timeout` argument
drives it, defaulting to 10 s. Retries are a property of the call path:
off in the cascade, where the next rung is the retry, and the engine
default for an explicit engine.
Cleanups from the #3252 review: `keyless` is derived from the client's
`api_key` type instead of a hand-set table flag; `engineIndex` no longer
depends on enum declaration order; one `searchKeyStatus` query replaces
three pub helpers for the REPL; the engine order and env-var list in the
tool description and `/searchEngine` help are generated from the table;
an empty result title renders as its URL instead of `****`.
Normalize key codepoints for the "Private Use Areas" keyboard.
Page holds a `input_modifiers` map so that multiple action sequence preserve
modifier keep states (this is only active with -Dwpt_extensions).
We now generate a keypress and synthentic click on specific keyboard events.
E.g. A trusted "enter" on keydown where the target is an anchor.
Improves various /uievents/ WPT cases.
MCP mode unconditionally set log.opts.format = .logfmt after the
--log_format flag had already been applied, so `--log_format pretty`
was silently ignored. It also ran after the "starting server" line,
which came out in the pre-override format in debug builds.
Move it into Config.logFormat() as a mode-aware default, mirroring
what logLevel() does for agent mode: null -> logfmt for mcp, and an
explicit flag wins.
clientX/clientY, screenX/screenY, pageX/pageY, layerX/layerY and the x/y
aliases returned the MouseEventInit double verbatim, so a synthetic event
built with clientX: 47.5 read back 47.5. Chrome floors these getters on
MouseEvent and its non-pointer subclasses (DragEvent, WheelEvent) and
exposes fractional coordinates on PointerEvent only; page code and
automation tooling written against Chrome assume integer mouse
coordinates. The getters now floor unless the event's _type tag says
pointer_event, so the inherited PointerEvent accessors keep the
fractional double.
Closes#3258
Reading el.draggable returned undefined for every element, and assigning
it created a plain JS own property with no reflection onto the content
attribute. Implement the reflected property per the HTML spec: the
"true"/"false" keywords are matched ASCII case-insensitively, the auto
state defaults to true for <img> and for <a> with an href (matching
Chrome), and the setter writes the literal "true"/"false" onto the
content attribute.
Closes#3255
Depends on https://github.com/lightpanda-io/wpt/pull/77
Track click count so that we can correctly fire a double click. On click, if
the mousedown and mouseup targets differe, find the nearest common inclusive
ancestor. Our `dispatchMouse` now sets the correct buttons value.
Also, HTMLElement.click() should be a PointerEvent (not a MouseEvent)
Improves various /uievents/ WPT cases.
Mirror the zenai provider pattern: the enum's declaration order is the
single source of truth for the .auto cascade, asserted at comptime against
api_engines (auto first, one table entry per API engine in tag order,
duckduckgo last with no entry). The enum reorders brave before tavily to
match the table's actual preference — persisted settings are unaffected
(ZON stores tag names).
Each table entry now carries a .keyless field; the hardcoded keenable
keyless rung, searchEngineKeyless, and searchExplicit's comptime gate all
derive from it, engineIndex collapses to arithmetic, and apiSearch takes a
typed ?[]const u8 key instead of anytype.
- build.zig.zon points at lightpanda-io/zenai's merge commit of zenai#9
(same tree, so the hash is unchanged)
- the keyless rung is no longer skipped when KEENABLE_API_KEY is set:
the public endpoint has its own rate-limit regime, so it is worth
trying after a failed keyed attempt — which the search tool
description already promised
- keenable retries are disabled: the fallback cascade is the retry
mechanism, and honoring the public endpoint's 60s Retry-After would
stall the search tool short of the DDG rung
- stale SearchEngine/searchExplicit doc comments updated for keyless
`has` add support for optional value (matches name AND value)
`delete` preserve entry order
`sort` compares by UTF-16
`forEach` iterates by index since the callback can mutate the list
Minor improvement to a handful of WPT tests, e.g.
/url/urlsearchparams-delete.any.html (first of a handful of /url/ WPT
improvements)
zenai's review round made keyless explicit in the client's type
(init takes ?[]const u8; null selects the public endpoint) and removed
the always-empty description field from results:
- the .auto keyless rung and searchExplicit pass null instead of "";
the null-key call is comptime-gated to keyless-capable engines and
apiSearch's key parameter is anytype to admit both shapes
- a set-but-empty KEENABLE_API_KEY is now keyed and fails loudly
instead of silently switching to the keyless rate-limit regime
- formatKeenableMarkdown reads snippet only (description no longer
exists to fall back to)
- build.zig.zon repinned to the reviewed zenai commit
Adds keenable to the search tool's engines (KEENABLE_API_KEY, tried
after brave/tavily/exa in .auto). Unlike the other engines it also
answers without any key: its client routes an empty key to the public
endpoint (rate-limited per client IP), so .auto now tries that as the
last rung before the DuckDuckGo scrape, and an explicit
/searchEngine keenable works keyless instead of erroring. Engine table
entries gain init_options so the Keenable client can carry the
lightpanda attribution header.
Needs the zenai Keenable search client (lightpanda-io/zenai PR #9); the
zon pin points at that branch until it lands.
No clue why this happens, but if the error mattered, a test would fail. A lot
of tests can be "harsh" on the server (e.g. a frame that navigates its parent
away while some resource load), so a WriteFailed error isn't exactly surprising