Commit Graph
9542 Commits
Author SHA1 Message Date
Karl Seguin 407b4ae91f Merge pull request #2269 from navidemad/fix-b7-attribute-value-escape-decode
selector: decode CSS escape sequences inside quoted attribute values
2026-04-28 07:27:16 +08:00
Karl Seguin 42f9abcdbf Merge pull request #2259 from navidemad/fix-a6-page-reload-replay-post
page: replay POST method/body/header on Page.reload
2026-04-28 07:16:50 +08:00
Karl Seguin 98b3b79e25 Merge pull request #2276 from lightpanda-io/fix-headers-init-leak
http: free curl header list on error
2026-04-28 07:14:14 +08:00
Navid EMAD 46f1646cf0 forms: honor formaction / formmethod / formenctype on submit button
When a form is submitted via a click on a submit button (or
form.requestSubmit(button)), the HTML form-submission algorithm requires
the submitter's formaction / formmethod / formenctype attributes to
override the form's corresponding attributes when present. Frame.submitForm
was reading action / method / enctype only from the form element, so the
button-side overrides were silently ignored. The symmetrical lookup for
formtarget already exists upstream — this commit applies the same pattern
to the other three attributes.

Closes #2278
2026-04-28 00:12:22 +02:00
Navid EMAD d4479c0383 ci: retrigger after pre-existing demo-scripts flake (lightpanda-io/demo#167) 2026-04-27 23:08:56 +02:00
Navid EMAD 1b9e8ad46c page: drop POST method/body on redirect so reload doesn't replay it
If a POST navigation gets redirected (302/303), the page that actually
loads is fetched with GET — but Frame._navigated_options still carries
the original POST method, body, and Content-Type. Page.reload would
then re-POST the form data to the redirect target, which is both
incorrect and dangerous (re-submission of credentials, charges, etc.).

Reset method/body/header on _navigated_options inside
frameHeaderDoneCallback whenever response.redirectCount() > 0. The full
spec-correct version would distinguish 307/308 (preserve method) from
301/302/303 (convert POST→GET), but resubmitting form data is the more
dangerous failure mode — conservative reset matches Chrome's practical
behavior on reload.

Also collapse the prev_body/prev_header extraction in doReload to a
single tuple-destructured blk: block (no behavior change).

Tests: new cdp.frame: reload after POST→redirect drops the POST drives
POST /redirect_to_echo → 302 → /echo_method, then Page.reload, asserts
the second request is GET. /redirect_to_echo route added to
testing.zig. The existing reload-replays-POST test still passes (no
redirect, POST is still replayed).
2026-04-27 22:47:55 +02:00
Navid EMAD 5b1452f162 Merge remote-tracking branch 'origin/main' into fix-a6-page-reload-replay-post
# Conflicts:
#	src/cdp/domains/page.zig
2026-04-27 22:39:54 +02:00
Navid EMAD 6e65487767 forms: extract Select.effectiveOption to centralize selectedness
Per review: collectForm walked the children once to look for a
non-disabled <option>, then Select.getValue walked them again to
emit the value. Extract the selectedness-candidate algorithm into
Select.effectiveOption() ?*Option so both call sites share it and
each runs a single iteration.

- Select.getValue is now a one-liner over effectiveOption.
- collectForm replaces the candidate-check + getValue pair with
  `select.effectiveOption() orelse continue; break :blk opt.getValue(frame);`.

Behavior is unchanged; the existing form_data.html fixtures
(selectWithoutOptions, selectMultipleWithoutOptions,
selectAllOptionsDisabled) still pass.
2026-04-27 22:25:55 +02:00
Navid EMAD fecab22ef0 forms: address review feedback on range clamp
- Use std.fmt.allocPrint in formatFloat (per-review suggestion).
- Drop the `r3.value = '1' -> '1'` fractional-bounds assertion, which
  conflicted with WHATWG step matching (default step=1, base=min=0.5,
  nearest valid is '1.5'). Step matching remains out of scope here.
2026-04-27 22:10:00 +02:00
Navid EMAD a5e5639a14 selector: own attributeValue result in-function, avoid double dupe
Per review feedback. attributeValue now guarantees an arena-owned
return: the quoted path already allocates into the arena via the
ArrayList, so we just dupe in the unquoted path and drop the redundant
arena.dupe at the call site. Pre-size the ArrayList to input.len since
escapes only shrink, skipping grow-from-zero reallocs.
2026-04-27 21:41:13 +02:00
Adrià Arrufat f99b0c8f25 Merge branch 'main' into agent 2026-04-27 17:39:06 +02:00
Adrià Arrufat fdadbaaad5 http: free curl header list on error 2026-04-27 17:32:01 +02:00
Karl Seguin ef3305a713 Merge pull request #2271 from lightpanda-io/disable_webgl_canvas
return null on getContext('webgl') and getContext('experimental-webgl')
2026-04-27 22:00:05 +08:00
Karl Seguin 65f3f90cab Merge pull request #2270 from lightpanda-io/terminate_guard
Mutex for Isolate.Terminate
2026-04-27 21:49:22 +08:00
Karl Seguin 1e61c76009 Merge pull request #2251 from lightpanda-io/same_url_navigate
Fix same-url navigate
2026-04-27 21:49:08 +08:00
Karl Seguin f515233b52 Merge pull request #2248 from lightpanda-io/blackhole_storage
Add and default to Blackhole storage
2026-04-27 21:48:54 +08:00
Adrià Arrufat a5f165a5a0 agent: improve grounding and prevent fabrication
Updates prompts to report access errors literally and forbids prior
knowledge fallback. Increases terminal tool result display limit.
2026-04-27 15:48:50 +02:00
Karl Seguin 36f89f0071 Merge pull request #2272 from lightpanda-io/uievents
Improve WPT uievents/ tests
2026-04-27 21:48:36 +08:00
Halil Durak 968cf5f9e5 Config: log-filter-scopes -> --log-filter-scopes 2026-04-27 16:06:29 +03:00
Halil Durak f4b220fb5f Config: fix --wait-script-file regression 2026-04-27 16:06:12 +03:00
Halil Durak 604210fd8b cli: introduce variants
Also refactors parsing by moving value parsing functionality to `parseValue` function.
2026-04-27 16:05:40 +03:00
Karl Seguin fcb9b00e09 Merge pull request #2243 from lightpanda-io/exit_on_cli_error
Propagate CLI parsing errors
2026-04-27 19:39:30 +08:00
Karl Seguin cb0ff4d07c Merge pull request #2257 from navidemad/fix-a15-location-pathname-search-setters
browser: trigger navigation when Location.pathname or .search is assigned
2026-04-27 18:31:09 +08:00
Karl Seguin 0bcb0e4e6a Merge pull request #2265 from navidemad/fix-a16-redirect-fragment-inherit
http: inherit request URL fragment across fragment-less redirect
2026-04-27 18:15:16 +08:00
Karl Seguin 9441e4fec8 Improve WPT uievents/ tests
1. Add a bunch of initXYZEvent functions, and fix the overload of initTextEvent
2. Add a number of missing (mostly legacy) accessors, e.g. uievent.getWhich()
3. CompositionEvent inherits from UIEvent, not Event
4. Give every accessor get/set a name. E.g the "name" of script.src is
  "get script" and "set script".
2026-04-27 15:57:11 +08:00
Karl Seguin a205bbbf7d return null on getContext('webgl') and getContext('experimental-webgl')
Currently, we return a dummy `WebGLRenderingContext`. But this type implements
virtually nothing. From MDN: "If the context identifier is not supported null is
returned."

Now, normally we like to move things along as much as we can. Returning
`WebGLRenderingContext` lets us know the next thing the code needs. But in this
case, it seems to be problematic. At least some code is defensive around a null
value from getContext('webgl'), but once we return an instance, it expects it
to work correctly. This is what causes https://github.com/features/copilot to
enter an endless JS loop.
2026-04-27 14:56:55 +08:00
Navid EMAD bc4fcdf8b9 selector: decode CSS escape sequences inside quoted attribute values
`Parser.attributeValue` walked quoted attribute values with
`indexOfScalarPos` to locate the closing quote and returned the raw byte
slice between the quotes — backslash escapes were neither honored as
escape boundaries nor decoded. As a result, `[data-x="abc\\def"]`
matched against the literal 8-byte string `abc\\def` instead of the
7-byte string `abc\def` the author intended, and `[data-x="foo\"bar"]`
truncated the value at the escaped inner quote.

Walk the string char-by-char respecting backslash escapes per CSS Syntax
Level 3 §4.3.5 (consume-string-token), reusing the existing
`parseEscape` helper that already powers `parseIdentifier` for `#id` /
`.class` selectors. Decode `\\`, `\"`/`\'`, and `\<hex digits>` (1–6
hex digits with optional whitespace terminator), drop `\<newline>` line
continuations, and surface bare newlines / trailing backslashes as
`InvalidAttributeSelector`.

Closes #2268
2026-04-27 08:33:05 +02:00
Adrià Arrufat a422a69770 agent: use safe truncation when pruning messages 2026-04-27 08:17:48 +02:00
Navid EMAD 7c14d1e29f forms: clamp <input type=range> value to min/max
Per the WHATWG HTML "input type=range" value sanitization algorithm, an
out-of-range value assigned via the JS `value` setter (or `Element.value`)
must be clamped to `[min, max]`, with `min`/`max` defaulting to 0 and 100
when the attribute is missing or not a valid floating-point number; if the
assigned value isn't a valid floating-point number, fall back to
`min + (max - min) / 2`. The previous `.range` branch in `sanitizeValue`
only validated the float grammar and returned `"50"` as a hardcoded
default, so `<input type="range" min="0" max="100">; el.value = "999"`
left `el.value === "999"` instead of `"100"`.

Step matching is a separate sanitization rule and intentionally out of
scope.

Closes #2266
2026-04-27 08:06:18 +02:00
Navid EMAD 00c42dec4e http: inherit request URL fragment across fragment-less redirect
Per RFC 7231 §7.1.2, when a 3xx response carries a Location header
without a fragment, the user agent must process the redirect as if
the value inherited the fragment of the request URL. URL.resolve
follows RFC 3986 §5.3 which drops the base fragment, so handleRedirect
now reattaches the original fragment when the resolved target has none.

Closes #2263
2026-04-27 08:04:47 +02:00
Navid EMAD 67239adb11 forms: skip FormData entry for <select> with no selectedness candidate
Per the HTML Living Standard "constructing the form data set" algorithm,
a <select> element appends one entry per option whose selectedness is
true. A non-multiple <select> derives its selectedness from an
explicitly-selected option, falling back to the first non-disabled
option in tree order. With zero options (or only disabled options), no
option is selected, so no entry should be appended.

The singular-select fast path in collectForm previously broke from the
block with select.getValue(frame) regardless of whether any option
existed, causing FormData to emit a phantom (name, "") entry for an
empty <select>. Guard the fast path on the existence of at least one
non-disabled <option> child and skip the entry otherwise.

Closes #2262
2026-04-27 08:04:21 +02:00
Adrià Arrufat 51b72e26b4 Config: derive AiProvider from zenai client 2026-04-27 08:03:48 +02:00
Navid EMAD 1d806475c4 page: make handleJavaScriptDialog drive confirm/prompt return values
Page.handleJavaScriptDialog previously responded -32000 "No dialog is
showing" regardless of whether a dialog was open, leaving CDP clients
no way to influence the JS-side return value of confirm() / prompt().
PR #2085 wired up the Page.javascriptDialogOpening event but explicitly
deferred the return-value override since true Chrome semantics require
suspending V8 mid-execution.

Add a pre-arm model that fits the auto-dismiss architecture without
runtime suspension: handleJavaScriptDialog stashes {accept, promptText}
on the BrowserContext; when the next JS dialog dispatches the
javascript_dialog_opening notification, the listener pops the stash and
fills it into the dispatch's response output param so Window.confirm /
prompt return the CDP client's choice. Without a pre-arm, headless
auto-dismiss values from PR #2085 are preserved (confirm->false,
prompt->null, alert->void).

Closes #2260
2026-04-27 07:08:01 +02:00
Adrià Arrufat 1d7e92beeb Merge branch 'main' into agent 2026-04-27 06:40:15 +02:00
Navid EMAD ea6b228f9d page: replay POST method/body/header on Page.reload
doReload built a NavigateOpts with only url + kind=.reload; method/body/header
defaulted to GET/null/null, so any prior POST navigation regressed to a GET
on reload. The HTML reload navigation re-fetches the document that produced
the current entry, and Chrome replays the same HTTP request that loaded the
page (including method, body, and Content-Type) — Lightpanda dropped all
three.

Retain the prior request body and content-type header in Frame.NavigatedOpts
(duped into the frame arena), and have doReload capture them into the CDP
command's arena before replacePage() frees the old frame. The reload's
frame.navigate call carries the replayed method/body/header so the request
the page was loaded with is the request that runs again.

Closes #2258
2026-04-27 06:28:58 +02:00
Karl Seguin 9fbade4573 Merge pull request #2253 from navidemad/fix-requestsubmit-default-submitter
Fix SubmitEvent.submitter for requestSubmit() with no argument
2026-04-27 12:20:36 +08:00
Karl Seguin 5dd1820f88 Merge pull request #2255 from navidemad/fix-network-cookies-empty-params-and-get-all
network: accept empty params on clearBrowserCookies, add getAllCookies
2026-04-27 12:15:58 +08:00
Navid EMAD 253efcae6e browser: trigger navigation when Location.pathname or .search is assigned
The JsApi binding for Location.prototype declared `pathname` and `search`
as read-only accessors (`null` setter), so assigning either had no
observable effect — the URL was never re-parsed and no navigation was
queued. Per HTML Living Standard §7.4.4.5.5/§7.4.4.5.6, both setters
must run the URL parser against a copy of the current URL and then
"Location-object navigate" to the result.

Add `setPathname` / `setSearch` member functions that mirror the
existing `setHash` shape: compute the new full URL via the matching
helper in `src/browser/URL.zig` and forward to `Frame.scheduleNavigation`
with `kind = .push` (matches Chrome's history-handling for these
setters). Wire them into the `pathname` / `search` accessors in place
of the previous `null` setters.

Closes #2256
2026-04-27 05:30:01 +02:00
Karl Seguin 6e729131ed Mutex for Isolate.Terminate
Improvement to https://github.com/lightpanda-io/browser/pull/2246

In that PR, the check for `v8__Isolate__IsExecutionTerminating` guarded
runMicrotasks from running when the execution was terminated, but what if
termination happens immediately after?  This adds a mutex to ensure that the
isolate cannot be terminated while microtasks are being processed.
2026-04-27 11:10:38 +08:00
Navid EMAD 53b41966fd network: send empty params test cases as raw JSON
The empty Zig anonymous struct `.{}` serializes to `[]` (tuple → JSON
array), not `{}`. The dispatch path's `InputParams.jsonParse` requires
the params field to begin with `object_begin`, so the previous test
fixtures hit `error.UnexpectedToken` → `error.InvalidJSON` instead of
exercising the production code paths.

Switch the two empty-params test cases to raw JSON string literals,
which the testing helper passes through unchanged (string literals are
pointer types and skip `std.json.Stringify.valueAlloc`). The production
code paths under test are unchanged.
2026-04-27 04:56:57 +02:00
Karl Seguin e30658fae1 fix test 2026-04-27 10:50:48 +08:00
Navid EMAD 5fba50a8d0 network: accept empty params on clearBrowserCookies, add getAllCookies
`Network.clearBrowserCookies` had an inverted-logic guard that returned
`InvalidParams` whenever the caller included a `params: {}` field — which
most CDP libraries (chrome-remote-interface, chromedp, etc.) do
unconditionally. The CDP spec defines no parameters for the method, but
JSON-RPC convention is to silently accept extra ones; Chrome and the
sibling `Storage.clearCookies` handler already do. Drop the guard.

`Network.getAllCookies` was missing from the `Network` dispatch enum and
returned `UnknownMethod`. Add a small handler that returns the entire
cookie jar via the existing `CdpStorage.CookieWriter`, mirroring
`Storage.getCookies` minus the `browserContextId` filter (Network
commands are scoped to the current browser context already).

Closes #2254
2026-04-27 04:10:20 +02:00
Navid EMAD 746921b86d Fix SubmitEvent.submitter for requestSubmit() with no argument
When Form.requestSubmit() is called without a submitter argument, the
HTML spec defaults the internal submitter variable to the form element
itself (step 2). The subsequent "submit a form element" algorithm then
collapses submitter === form back to submitterButton = null before
constructing the SubmitEvent. We were skipping that collapse and using
the form as the SubmitEvent's submitter, which diverges from Chrome /
Firefox / WPT.

Frame.submitForm now coerces submitter_ to null when it equals the
form element, mirroring the spec algorithm. The form.html fixture
that previously asserted the wrong behavior is updated to assert
.submitter === null per spec.

https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-form-submit

Closes #2252
2026-04-27 03:44:33 +02:00
Karl Seguin a2f21ff463 Fix same-url navigate
Same-URL navigate should always cause a reload. The code that currently prevents
a navigate on fragment change is too loose and treats identical URLs as being
a fragment change..but for it to be considered a fragment change, the fragment
actually has to change.

This improves some WPT compat where tests do:

<iframe></iframe>  <--- loads "about:blank"

<script>
document.querySelector('iframe').src = "about:blank"; <--- should load it again
</script>
2026-04-27 09:27:30 +08:00
Karl Seguin a578f4d6ad Merge pull request #2246 from lightpanda-io/terminate
Adds --terminate-ms command line argument + ctrl-c improvements in fetch
2026-04-27 08:57:45 +08:00
Karl Seguin 945e9597bf Propagate CLI parsing errors
Rather than continue on a failed argument parse, propagate the error so that
the program eventually exists.
2026-04-27 08:48:30 +08:00
Karl Seguin e1e9a0d78c Merge pull request #2244 from navidemad/fix/get-element-by-id-from-node-recovery
Fix Frame.getElementByIdFromNode to recover from removed_ids
2026-04-27 08:46:05 +08:00
Matt Van Horn 1f802d034e test(webapi): add HTMLFrameSetElement regression test
Closes the test gap on the FrameSet stub. Asserts that
HTMLFrameSetElement exists as a global function, that <frameset> in
markup yields an HTMLFrameSetElement instance with the standard
tagName/toString/prototype-chain shape, and that document.createElement
('frameset') round-trips the same.

Refs #2249
2026-04-26 15:45:29 -07:00
Matt Van Horn 325a1e8224 feat(webapi): add HTMLFrameSetElement stub
Angular 9's zone.js polyfill calls HTMLFrameSetElement during
__load_patch, but the constructor was undefined in Lightpanda. The
fetch in #2249 dies on the very first patched API.

Add a minimal stub mirroring the existing HTMLDirectoryElement /
HTMLBodyElement pattern: a Zig struct with `_proto: *HtmlElement`,
asElement / asNode helpers, and a JsApi block exposing the bridge as
HTMLFrameSetElement. Registration goes in the same five files every
HTML element touches: the new file under
webapi/element/html/, an import + union field in Html.zig, the bridge
list in js/bridge.zig, the Tag enum + tag-name mappings in
webapi/Element.zig, and the default-display block list in
CSSStyleDeclaration.zig.

No tests; the build itself validates the registration paths.

Closes #2249
2026-04-26 15:38:51 -07:00
Adrià Arrufat 138665d746 agent: update zenai dependency 2026-04-26 17:40:48 +02:00