Commit Graph

8321 Commits

Author SHA1 Message Date
Adrià Arrufat
2c4803b1cf mcp: ensure clean http server shutdown
Track active connections and shut down sockets during deinit to unblock
pending reads. Drain the job queue properly before stopping the worker.
Also fix `--http-port` references to `--port` in docs and errors.
2026-07-13 10:07:37 +02:00
Adrià Arrufat
6039bc82b8 docs: correct --host description
Co-authored-by: Karl Seguin <karlseguin@users.noreply.github.com>
2026-07-13 09:28:25 +02:00
Karl Seguin
df0473420c ops: Reduce logging of JsException on event dispatch
Event dispatch using a try/catch (tryCallWithThis) so that the EventManager can
decide how to handle the error. Previously, using the `callWithThis` would
result in always logging the error. Now, the EventManager can skip logging
JsExceptions.
2026-07-13 13:20:01 +08:00
Karl Seguin
f4da23da2e ops: Don't log TryCatchRethrow errors
error.TryCatchRethrow is used as a control flow (sorry). It signals that an
error has already been throw in v8 and that the method should exit (ultimately,
it ends up being the return value passed to our v8 bridge, which knows to ignore
it). We shouldn't log this as a WARN, it's completely normal that it happens AND
it really contains no meaningful information as-is.
2026-07-13 12:59:29 +08:00
Karl Seguin
e4387bf7fa minor, webapi: Prevent outerHTML of document's element's from being written
Captured by WPT test: /domparsing/outerhtml-01.html
2026-07-13 12:52:38 +08:00
Karl Seguin
f7f8869ed3 webapi, minor: XHR Response-Type and invalid JSON parsing tweaks
Add an explicit 'default' ResponseType, which mostly behaves like `text`, but
can be used do discriminate between an explicit text and the default value.

Also, on JSON parse error, return null, not an exception.
2026-07-13 12:28:59 +08:00
Karl Seguin
7b8ea39164 webapi: Improve DOMRect
We had a basic DORect implementation (which probably covered more cases). This
commit extends the implementation to be more complete:

- Add DOMRectReadOnly (which DOMRect inherits from)
- Add StructuredClone support to DOMRect and DOMRectReadOnly
- Make DOMRect and DOMRectReadOnly usable from Worker

DOMRect can be on the hot path with IntersectionObserver, so we maintain a
value-based data object (DOMRect.Data) for internal use (things returned to js
always have to be heap allocated (and always were, we're just more explicit
about this part now)).
2026-07-13 12:03:52 +08:00
Karl Seguin
5e2615850d webapi: Allow CustomElements to extend any HTMLElement interfaces.
Pervious, extension was limited to HtmlElement. CustomElements can now extend
any html element interface.

The implementation isn't great. This is a special case, and rather than coming
up with some abstract way to define this in bridge, we make the Snapshot
aware of this specific case directly.
2026-07-13 11:13:49 +08:00
Karl Seguin
0ff08420c8 Merge pull request #2917 from lightpanda-io/svg
webapi: Add various SVG types
2026-07-13 07:49:18 +08:00
Karl Seguin
a3dd3f254d mem: Make Request, URLSearchParams finalized
Also, fix potential UAF in FormData iterator
2026-07-13 07:36:16 +08:00
Karl Seguin
f444f2cf6d Merge pull request #2898 from lightpanda-io/js_global_rework
refactor: Rework how v8::Globals are managed
2026-07-13 07:20:37 +08:00
Adrià Arrufat
08eecfbbac cli: add run command for script replay
`lightpanda agent <script>` replays a saved script token-free (no LLM),
but running it under `agent` reads wrong. Add `run` as a dedicated,
script-only command: `lightpanda run <script>`.

`run` exposes only a required [SCRIPT] positional plus the common
options; its LLM-session flags (--task/--save/--list-models/REPL) are
absent, so they're rejected at parse time. `parseArgs` normalizes a
`run` invocation to an `.agent` command, reusing the existing token-free
replay path unchanged, so nothing downstream needs a `.run` case.

`agent <script>` keeps working. Telemetry records `run` as its own mode
code (R) via a new Config.command field, so its adoption is measurable
separately from agent script replay (AR).
2026-07-12 23:37:23 +02:00
Adrià Arrufat
bdb122cdce Merge branch 'main' into agent-streaming 2026-07-12 22:56:54 +02:00
Adrià Arrufat
70b5d400fa build: bump zenai dependency 2026-07-12 22:56:27 +02:00
Adrià Arrufat
46be87f2ca test(agent): cover resolveStream precedence and stream round-trip
Verify the stream default is on, that a remembered value wins, and that
the field parses from (and is absent-safe in) .lp-agent.zon.
2026-07-12 22:13:49 +02:00
Adrià Arrufat
fa2dc02721 fix(agent): reset stream_active at turn start
`stream_active` relied on every runTools exit path balancing pause with
`endStreamedText`. If one is ever missed, a stale-true flag skips the
spinner pause on the next turn, interleaving frames with streamed text.
Reset it alongside `streamed_text` so the invariant is local.
2026-07-12 22:10:29 +02:00
Adrià Arrufat
2e8e0d1a6f docs(agent): note why the streamed_text guard is turn-wide safe
Document the invariant that lets a single per-turn flag suppress the
buffered reprint without risking a dropped final answer.
2026-07-12 22:10:11 +02:00
Adrià Arrufat
a64ced0ccc fix(agent): stream assistant text only in the REPL
Streaming defaulted on for every mode, so one-shot `--task` and script
runs interleaved intermediate assistant commentary into stdout, which
wrappers treat as the answer. Gate the stream hook on `Terminal.isRepl`
so non-interactive modes keep the buffered final answer.
2026-07-12 22:09:48 +02:00
Adrià Arrufat
5abae593d8 fix(agent): keep streaming off for suppressed turns
Streaming wrote assistant deltas straight to stdout regardless of
`suppress_answer`, so `--save`/capture turns (which set it to hide the
answer) leaked model text past the `runTurn` guard. Pass a null stream
hook when the turn's answer is suppressed.
2026-07-12 22:09:25 +02:00
Adrià Arrufat
f66f0c191d mcp: add HTTP transport and multi-session support
Introduces an HTTP transport option to serve multipleagents from a
single process. Each connection is routed to its own isolated
browsing session using the `Mcp-Session-Id` header.

Also adds new session management tools (`session_new`, `session_list`,
`session_close`) and refactors the MCP server to support multiple V8
isolates with parking.
2026-07-12 15:27:20 +02:00
Karl Seguin
2649b4c6cf Merge pull request #2841 from lightpanda-io/indexeddb
webapi: IndexedDB base
2026-07-12 07:31:37 +08:00
Karl Seguin
1967be7e54 webapi: Add various SVG types
Rather than having a generic SVG type (which gets us past parsing, but even the
slightest JS usage is likely to fail), this adds concrete types for a number of
known SVG types. A lot of these are empty (but even that's enough to pass
something like `instanceof SVGAElement`), but a handful of the more important
accessors and methods are implemented (e.g. I ran into a site that made
extensive use of SVGSVGElement.getElementById).
2026-07-11 18:31:09 +08:00
Karl Seguin
ca927adff2 Merge pull request #2916 from lightpanda-io/attribute-frame
fix: When mutating attributes, use element's owner frame
2026-07-11 09:29:58 +08:00
Karl Seguin
d43d11a641 Merge pull request #2912 from lightpanda-io/agent-repl-fixes
agent: multi-line slash commands and --save .js extension
2026-07-11 07:26:29 +08:00
Karl Seguin
c2d4dbebc2 fix: When mutating attributes, use element's owner frame
Currently, we use the calling frame when mutating an element's attributes (e.g.
add/delete). This commit changes it to the element's owning frame. This should
fix both some errors and potential uaf.
2026-07-11 07:20:58 +08:00
Adrià Arrufat
40b902fed5 build: update zenai dependency 2026-07-11 00:52:58 +02:00
Adrià Arrufat
049c5f08f2 feat(agent): add assistant text streaming 2026-07-11 00:50:01 +02:00
Francis Bouvier
15a8a81e64 js: support definer/descriptor interceptors; fix HTMLCollection expandos
Fixes the remaining WPT /dom/collections failures around setting,
defining and describing HTMLCollection properties:

- HTMLCollection-own-props.html: assigning to a supported named property
  or to any array index must fail (TypeError in strict mode), while
  expandos for unsupported names must keep working.
- HTMLCollection-supported-property-indices.html: indexed property
  descriptors must be {enumerable: true, configurable: true, writable:
  false}, Object.defineProperty on any array index must throw, and
  deleting a supported index must fail.
- HTMLCollection-supported-property-names.html: named property
  descriptors must be {enumerable: false, configurable: true, writable:
  false} ([LegacyUnenumerableNamedProperties]) and defineProperty on a
  supported name must throw.

Bridge changes, mirroring the existing interceptor callbacks:

- bridge.Indexed gains a definer callback; bridge.NamedIndexed gains
  definer and descriptor callbacks, exposed through new indexedFull and
  namedIndexedFull builders so existing indexed/indexedReadWrite/
  namedIndexed call sites are untouched. Definer and descriptor
  handlers reuse the deleter (name -> bool) and getter (name -> value)
  calling shapes. The stale void-returning definer/descriptor typedefs
  in the v8 binding are bridged with @ptrCast; v8 actually reads the
  returned u32 as v8::Intercepted.
- Caller's query helpers (shared queryReturn) now accept, besides the
  existing bool shape, query callbacks returning the
  v8.PropertyAttribute bits directly (e.g. ReadOnly), with
  error.NotHandled falling through to the ordinary lookup.

HTMLCollection registers:

- an indexed setter and definer that always fail (no indexed setter in
  Web IDL), and an indexed query reporting ReadOnly for supported
  indices;
- a named definer that fails for supported names, and a named
  *descriptor* (not query) reporting {writable: false, enumerable:
  false, configurable: true}. Using the descriptor callback instead of
  query is deliberate: the ordinary [[Set]] path does not consult it, so
  assigning through a derived object (HTMLCollection-as-prototype.html)
  still creates an expando on the receiver, as the spec's
  ignore-named-props rule requires, while direct assignments fail via
  the definer.

Coverage: /dom/collections is now fully green (53/53):
- HTMLCollection-own-props.html 4/8 -> 8/8
- HTMLCollection-supported-property-indices.html 4/7 -> 7/7
- HTMLCollection-supported-property-names.html 5/6 -> 6/6
- HTMLCollection-as-prototype.html stays 2/2

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 17:34:13 +02:00
Francis Bouvier
809b36d96a webapi: enumerate collection supported properties for ownPropertyNames
Fixes the Object.getOwnPropertyNames related failures in WPT
/dom/collections:

- HTMLCollection-live-mutations.window.html: "ownPropertyNames" case
- HTMLCollection-supported-property-names.html: 3 getOwnPropertyNames cases
- namednodemap-supported-property-names.html: all 3 cases
- domstringmap-supported-property-names.html: all 5 cases

Object.getOwnPropertyNames returned no interceptor-backed properties
because these collections registered no enumerator callbacks.

Using the bridge's existing indexed and named enumerator support:

- HTMLCollection registers an indexed enumerator (the supported
  indices) and a named enumerator implementing the spec's supported
  property names: for each element in tree order, its non-empty id and,
  for HTML elements, its non-empty name attribute, without duplicates.
- NamedNodeMap registers indexed and named enumerators (attribute
  qualified names in order, via List.getNames).
- DOMStringMap registers a named enumerator (camel-cased data-*
  attribute names). The previously unused kebabToCamel helper is fixed
  to follow the spec conversion: only a '-' followed by an ASCII
  lowercase letter is folded to an uppercase letter, a bare "data-"
  attribute maps to the empty name, and a trailing '-' is preserved.

(As originally written, this commit also added named enumerator
interceptor support to the js bridge; main gained equivalent support
independently, so the rebase keeps main's bridge API.)

Coverage:
- /dom/collections/HTMLCollection-live-mutations.window.html 4/5 -> 5/5
- /dom/collections/HTMLCollection-supported-property-names.html 2/6 -> 5/6
- /dom/collections/namednodemap-supported-property-names.html 0/3 -> 3/3
- /dom/collections/domstringmap-supported-property-names.html 0/5 -> 5/5

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 17:19:19 +02:00
Francis Bouvier
01a16fe66e webapi: supported HTMLCollection properties can't be deleted
Fixes 2 failing tests in WPT /dom/collections/HTMLCollection-delete.html
("Strict id" and "Strict name"): deleting a supported indexed or named
property of an HTMLCollection must fail (and throw a TypeError in strict
mode), since those properties are backed by the live collection.

HTMLCollection registered no deleter interceptors, so `delete c[0]` and
`delete c.name` fell through to the ordinary [[Delete]] which succeeded
on the (non-own) property.

HTMLCollection now registers an indexed and a named deleter (via the
bridge's indexedReadWrite/namedIndexed deleter callbacks) that return
false for supported properties - which v8 turns into a TypeError in
strict mode - and error.NotHandled for unsupported ones, leaving those
to the ordinary path.

(As originally written, this commit also added indexed setter/deleter
interceptor support to the js bridge; main gained equivalent support
independently, so the rebase keeps main's bridge API.)

Coverage: /dom/collections/HTMLCollection-delete.html 2/4 -> 4/4.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 17:09:42 +02:00
Francis Bouvier
cc81c5993b webapi: don't fire AbortSignal.timeout() after frame detach
Fixes the failing test in WPT /dom/abort/abort-signal-timeout.html:
"Signal returned by AbortSignal.timeout() is not aborted after frame
detach".

The scheduler task registered by AbortSignal.timeout() kept running
after the iframe that created the signal was removed from the document,
so the signal aborted and the abort event fired. Per the DOM spec, the
abort is queued as a global task on the signal's relevant global, and
such tasks must not run once the global's document stops being fully
active.

The timeout callback now walks the signal's frame and its ancestors and
skips the abort when a hosting iframe element is no longer connected.

Coverage: /dom/abort/abort-signal-timeout.html 0/1 -> 1/1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 16:53:15 +02:00
Francis Bouvier
1d263aabbd webapi: keep explicit null as AbortSignal abort reason
Fixes 1 failing test in WPT /dom/abort/event.any.html (and its .worker
variant): "AbortController abort(null) should set signal.reason".

abort() reason parameters were typed ?js.Value.Global, and the JS->Zig
conversion collapses an explicit JS null into a missing argument, so
controller.abort(null) fell back to the default "AbortError"
DOMException. Per the DOM spec, the abort reason defaults to a new
AbortError only when the reason is not given (undefined); an explicit
null must be stored as-is.

The parameters are now ?js.Value, which preserves the missing vs
explicit-null distinction, and the new AbortSignal.reasonFromJs helper
only falls back to the default for a missing or undefined reason.

Coverage: /dom/abort/event.any.worker.html 15/16 -> 16/16,
/dom/abort/event.any.html 15/16 -> 16/16.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 16:47:42 +02:00
Francis Bouvier
7d23b62573 webapi: fix AbortSignal dependent-signal abort semantics
Fixes 3 failing tests in WPT /dom/abort/abort-signal-any.any.html (and
its .worker variant):

- "Dependent signals are aborted correctly for reentrant aborts": when a
  source signal aborted a second source from within an abort listener,
  the shared dependent signal dispatched its abort event twice. Per the
  DOM spec, only dependent signals that are not already aborted are
  appended to the list of signals to dispatch. Dependend.markAborted now
  reports whether the dependent was newly marked, and abort() only
  dispatches for those.

- "Dependent signals should use the same DOMException instance" (already
  aborted source / source aborted later): Reason.dom held a DOMException
  by value, so every .reason access wrapped a fresh JS object and the
  identity check source.reason === dependent.reason failed. Reason.dom
  is now a *DOMException allocated once at abort time; the JS bridge
  identity-maps the pointer so the source and all dependents expose the
  very same DOMException instance.

Coverage: /dom/abort/abort-signal-any.any.worker.html 11/14 -> 14/14,
/dom/abort/abort-signal-any.any.html 11/14 -> 14/14.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 16:42:51 +02:00
Adrià Arrufat
897b446401 Merge pull request #2914 from lightpanda-io/save-intent-comments
agent: emit intent comments in saved scripts
2026-07-10 16:41:30 +02:00
Adrià Arrufat
7547d3ac72 agent: emit intent comments in saved scripts
Instruct the /save synthesis model to annotate the generated script with
short `//` intent comments — one per logical block (navigate, extract,
fan out, aggregate, return) describing what the block accomplishes, so a
later reader (human or agent) grasps the script at a glance.

The save synthesis prompt previously ended with "no commentary", which
discouraged code comments; reword it to forbid only prose/markdown fences
outside the code while explicitly asking for the `//` intent comments. The
same prompt backs the MCP `save` tool, so both surfaces benefit. Also add a
matching best-practice bullet + example to the agent's script-writing skill.
2026-07-10 16:12:16 +02:00
Karl Seguin
034a3c5bee Merge pull request #2913 from lightpanda-io/attribute-lookup-fix
fix: broken attribute lookup cross frame
2026-07-10 19:05:08 +08:00
Karl Seguin
24ddb4ac39 fix: broken attribute lookup cross frame
https://github.com/lightpanda-io/browser/pull/2901 introduced optimizations to
how attributes are stored and probed. One of those optimization breaks when
an attribute is probed across iframes.

This commit removes that specific optimization and reverts to a linear scan
comparing the strings (which is still faster after 2901 since it's an array and
not a linked list).
2026-07-10 18:26:52 +08:00
Adrià Arrufat
03f6577efa agent: continue slash commands across lines inside '''...''' blocks
The docs promise triple-quoted values that span multiple lines, but the
REPL dispatched every physical line immediately, so the multi-line
/extract form died with 'unterminated quote' on its first line. A slash
command that leaves a '''...''' block open now keeps reading lines at a
continuation prompt until the block closes; Ctrl-D abandons it.
2026-07-10 11:55:50 +02:00
Adrià Arrufat
0f686381dd agent: append .js to --save destinations like /save does
The one-shot --save flag bypassed save.parseCommand, so '--save test'
wrote a file literally named 'test' while the REPL's /save appended the
extension. Share one ensureJsExtension helper so the paths can't drift.
2026-07-10 11:34:53 +02:00
Karl Seguin
86c8b88328 httpclient: eliminate some uaf on transfer abort
Commit eliminates a class of UAF on transfer abort due to the abort potentially
JS callbacks within a libcurl callback.

It also introduces a graveyard queue to the HttpClient so that, on deinit,
Transfers become detached but their memory is still valid. In debug, the
transfer is poisoned so that any uaf fails loudly. In release, the graveyard
is simply cleaned up.
2026-07-10 12:34:45 +08:00
Karl Seguin
377de019b8 fix uaf when aborting a robots-parked transfer 2026-07-10 08:22:28 +08:00
Karl Seguin
cca9bddead fix typos in comments 2026-07-10 07:51:33 +08:00
Karl Seguin
2d88c4a907 Protect against v8 stackoverflow when with sibling blocking request
A NSFW site was reported as crashing. It had 21 sibling iframes all loading
the same synchronous script. This would overflow the v8 stack. The issue is
that, on a blocking request, we gate other requests from the same frame, but not
from other frames. Typically, that's ok / what we want. BUT, for a document
request, we need to be more careful:

1- We're inside a v8 callback (so some v8 stack is active)
2- A sync request is made
3- while pumping the http client, a document for another frame completes
4- Ok, it's on another frame, we don't block it
5- We start the parser
6- The parser encounters iframs
7- Each of those iframes have a blocking script
8- Those blocking scripts load their own document

See what's happening? We're still in the v8 callback of #1, still on that same
stack, and we're loading more and more heavy parsers and triggering more and
more http callbacks.

It's possible this problem extends beyond document parsing. But this is the
one we observed and it's also the most likely to cause recursive blocking
requests.
2026-07-10 07:35:37 +08:00
Karl Seguin
941935af56 fix inverted robots check 2026-07-10 07:35:36 +08:00
Karl Seguin
2eab4d2630 refactor: HttpClient
Replaces layering with an inline request pipeline, and transfer queue. This is
meant to simplify the code, reduce footguns, and make future enhancements easier
to implement (e.g. speculative parsing (which requires streaming to fully
leverage)).

Previously, HttpClient implemented deferring as a layer which required special
pumping at various callsites (https://github.com/lightpanda-io/browser/pull/2855,
https://github.com/lightpanda-io/browser/pull/2843, ...). In this new approach,
deferring is built-into the HttpClient/Transfer's flow. Specifically, Transfers
now maintain a queue of events (start, header, data, end, err) which are
dispatched in HttpClient.tick. The result is that JS callbacks are never
executed in the same stack that initiated the I/O, without needing guards or any
external intervention.

tTwo other benefits come from this. The first is that reentrant libcurl is
eliminated. Instead of "libcurl -> callback", it's now "libcurl -> transfer
event queue THEN  tick -> callback" (we don't have to wait until the NEXT tick, we
can just do it later in the tick). HttpClient still has to guard against libcurl
reentrancy, but only because of how WebSocket is implemented, and we should be
able to unify WebSockets to use an event queue too in a follow up PR (which will
eliminate a bunch of guard code).

The transfer queue should also be useful to re-implement streaming, since a
data chunk is just an event in the transfer's event queue. For now, I kept it
as a single buffered event to minimize the change. But since speculative parsing
depends on this, and speculative parsing seems to be the next major performance
tweak we can make, we need to re-introduce streaming.

The other change is the removal of all other layers in favor of a pipeline. This
works well with the existing Transfer.park mechanism, where a parked Transfer
can restart the pipeline for a transfer in an arbitrary point (not as fancy as
it sounds given how simple the flow is). The fallout from this is that we're no
longer creating/wrapping contexts and callbacks: whatever the request was
configured with is all we need.

Because of this, HttpClient.Response is removed. There are no intermediary
responses and no changing context, everything is just the Transfer.

A smaller change is the addition of newRequest + transfer.submit(). The one-shot
HttpClient.request and HttpClient.requestT still exist, but this explicit create
+ submit has some advantage. First, callers can use the transfer.arena (e.g.
Frame using the transfer's arena to set the Referrer header). Second, callers
can holds Transfer immediately, rather than waiting for their startCallback to
be fired. An abort on an XMLHttpRequest called before the start of the transfer
no longer silently fails.
2026-07-10 07:35:36 +08:00
Karl Seguin
9500c6a653 Merge pull request #2909 from lightpanda-io/fetch-redirect-mode
fetch: honor redirect option (manual and error modes)
2026-07-10 07:24:41 +08:00
Karl Seguin
40504ad671 Merge pull request #2901 from lightpanda-io/attribute_datastruct
perf, mem: Embed Element Attributes and replaced linked list with array
2026-07-10 07:18:28 +08:00
Karl Seguin
5913f37c30 Merge pull request #2907 from lightpanda-io/improve-storage-api
webapi: Improve storage WebAPI
2026-07-10 07:17:21 +08:00
Karl Seguin
300d31c338 Merge pull request #2896 from lightpanda-io/js-watchdog
feature: add watchdog for JS hangs
2026-07-10 07:16:39 +08:00
Karl Seguin
24e8e731fa Merge pull request #2906 from lightpanda-io/v8-memory-limit-headroom
mem, v8: Increase the extra room we give v8 @ nearHeapLimit
2026-07-10 07:16:26 +08:00