Commit Graph

8347 Commits

Author SHA1 Message Date
Pierre Tachoire
1f85ff1653 cdp: return session id with Browser.setDownloadBehavior 2026-06-19 11:51:42 +02:00
Armaan Sandhu
ab4d702445 address review: Browser-only download events, Header param parsing, real url/basename, deny default 2026-06-19 11:51:41 +02:00
Armaan Sandhu
8e8a9b2213 fix(cdp): make Browser.setDownloadBehavior a no-op without a loaded context
Playwright sends Browser.setDownloadBehavior at the browser level during
connection setup, before any target/context exists. Returning an error there
aborted the whole connection, failing every playwright demo-runner test.
Treat the no-context case as a success no-op, matching the prior behavior.
2026-06-19 11:50:31 +02:00
Armaan Sandhu
8ba54850db feat(cdp): implement Browser.setDownloadBehavior file downloads
Browser.setDownloadBehavior was a noop, so Lightpanda had no file-download
path. A response with Content-Disposition: attachment is now streamed to disk
under downloadPath, and Page.downloadWillBegin / Browser.downloadProgress are
emitted when eventsEnabled.

Fixes #2701
2026-06-19 11:50:29 +02:00
Karl Seguin
de046cec36 Merge pull request #2784 from lightpanda-io/agent-task-save
agent: add --save flag to synthesize scripts from tasks
2026-06-19 17:27:09 +08:00
Adrià Arrufat
98a5458205 agent: allow paths in --save option 2026-06-19 07:46:42 +02:00
Karl Seguin
36fec926cc guard against post-close navigate 2026-06-19 13:01:42 +08:00
Karl Seguin
762e21a8f3 uaf: Extend closed popup's lifetime from Frame to Page
Follow up to https://github.com/lightpanda-io/browser/pull/2742

Where 2742 dealt with window-reuse across navigation, this commit addresses
window lifetime after a .close() (so, specifically for popups). I was hoping for
a fancier solution which would let us maintain eager release with safety, but
instead opted for the much simpler "window lifetime is tied to the page".

There's a couple reasons for this. It _is_ more consistent with how everything
else works (Workers, and iframes), but the real reason is that we lack the
infrastructure to do this safely/cleanly.
2026-06-19 11:43:18 +08:00
Karl Seguin
de5b9364e6 cleanup: Break various frame behavior into their own little utility files
This is purely mechanical and was driven almost entirely by Claude. The goal is
simply to break Frame.zig into small / more cohesive components. So instead of
having ~10 observer-specific methods on Frame, they now live in Frame.observers.

In general, our code always takes *Frame as the last parameter (a consequence
that this is the easiest / less ambiguous way to auto-inject a Frame into a
webapi). But, for these, I kept it as the first parameter because it's still
the "receiver" even though the dot syntax isn't applicable.
2026-06-19 10:11:17 +08:00
Karl Seguin
6b42cad3db Merge pull request #2727 from rohitsux/feat/cdp-browser-permissions
feat(cdp): honor Browser.grantPermissions / setPermission / resetPermissions
2026-06-19 07:49:41 +08:00
Rohit
a0fc38eb6d fix(cdp): store permissions at the browser level
Address review: permissions set via Browser.grantPermissions / setPermission
now live on the Browser instead of the active Page, so they persist across
page navigations (previously a navigation dropped them) and match how Chrome
scopes permissions to the browser context. navigator.permissions.query() reads
the browser-level state. Also log not_implemented for the origin and
browserContextId params, which are accepted but not yet honored.
2026-06-19 07:30:16 +08:00
Karl Seguin
3335c746dc switch permission value/state from string to enum 2026-06-19 07:28:36 +08:00
Rohit
697ae06852 feat(cdp): honor Browser.grantPermissions / setPermission / resetPermissions
These three Browser methods were noops, so a CDP client could not change
what navigator.permissions.query() reports - it always returned "prompt".

Store permission state on the active Page (keyed by permission name) and
have navigator.permissions.query() read it back. grantPermissions sets each
listed permission to "granted", setPermission sets a single permission to an
explicit state, and resetPermissions clears them (query falls back to
"prompt"). State is scoped to the Page and resets on navigation.

Adds a CDP round-trip test exercising all three methods.
2026-06-19 07:28:05 +08:00
Karl Seguin
56795e3c0b Merge pull request #2782 from lightpanda-io/scope-filter-improvement
support +/-/all syntax in --log-filter-scopes
2026-06-19 07:19:43 +08:00
Karl Seguin
deeb136ab2 Merge pull request #2786 from lightpanda-io/cleanup_tests
cleanup, minor: remove weird fake-tags
2026-06-19 07:16:40 +08:00
Karl Seguin
a1ce1ae791 cleanup, minor: remove weird fake-tags
Cleanup of https://github.com/lightpanda-io/browser/pull/2779 which inserted
unnecessary tags.
2026-06-19 07:07:44 +08:00
Pierre Tachoire
d0d2048e11 Merge pull request #2771 from lightpanda-io/clone-postmessage
webapi: structured-clone window/MessagePort postMessage messages
2026-06-18 16:46:35 +00:00
Adrià Arrufat
9df4537109 Merge pull request #2783 from lightpanda-io/extract-absolute-urls
extract: resolve href/src attributes to absolute URLs
2026-06-18 18:01:34 +02:00
Adrià Arrufat
56e4401db4 extract: resolve href/src attributes to absolute URLs
The schema walker returned raw attribute values, so `attr: "href"` /
`attr: "src"` yielded whatever the markup contained — usually a relative
URL. That is inconsistent with the links and structuredData tools (which
resolve against the document base) and with the DOM .href/.src
properties, and it makes the common extract-then-goto pattern fail on
sites with relative links, since goto needs an absolute URL.

Resolve href/src via `new URL(raw, document.baseURI)`, falling back to
the raw value on parse failure. Other attributes pass through unchanged;
absolute URLs are unaffected.
2026-06-18 17:46:06 +02:00
Adrià Arrufat
69d302fabe agent: add --save flag to synthesize scripts from tasks 2026-06-18 17:45:26 +02:00
Pierre Tachoire
a83b9b1e7e Merge pull request #2781 from lightpanda-io/agent-help-providers
agent: list Vercel, Mistral, llama.cpp in --help
2026-06-18 13:20:46 +00:00
Pierre Tachoire
828444675f feat(log): support +/-/all syntax in --log-filter-scopes
`--log-filter-scopes` previously took a comma-separated list of scopes to
suppress. Extend it with explicit include/exclude directives applied
left-to-right:

  -X     filter out scope X
  X      alias for -X (backward compatible)
  +X     filter in scope X
  all    target every scope

This makes "filter everything except one scope" expressible, e.g.
`-all,+cdp` suppresses all logs except `cdp`. Bare scope names keep their
old meaning, so existing invocations like `http,unknown_prop` are
unaffected.

Internally, the suppressed-scope list is replaced by a resolved per-scope
`scope_enabled` boolean array built via `log.resolveFilterScopes()`, so the
hot-path `enabled()` check is a single array index. Filtering remains
Debug-only, as before. `testing.LogFilter` is adapted to the new
representation; all existing call sites are unchanged.

Adds a unit test for resolveFilterScopes (default, backward-compat,
-all,+cdp, ordering) and updates the help text.
2026-06-18 14:42:07 +02:00
Adrià Arrufat
1cfdd9ae3b agent: list Vercel, Mistral, llama.cpp in --help
The agent already supports these providers (via zenai), but the agent
command's help text still listed only anthropic/openai/gemini/huggingface/
ollama. Bring --help in sync:

- Add vercel, mistral, and llama_cpp to the --provider allowed values
- Add AI_GATEWAY_API_KEY and MISTRAL_API_KEY to the auto-detect and
  env-key lists
- Document llama.cpp as a keyless local server (base http://localhost:8080/v1)
- Note that a provider can set its own effort default (Mistral -> none)

This realigns --help with the docs at lightpanda.io/docs/usage/agent.
2026-06-18 13:48:49 +02:00
Adrià Arrufat
8bcd0a9567 Merge pull request #2780 from lightpanda-io/agent-default-effort
agent: support provider-specific default effort
2026-06-18 13:35:13 +02:00
Adrià Arrufat
59dc69f1fe agent: support provider-specific default effort
Updates the zenai dependency and resolves the agent's effort based on
the configured provider's default. Also dynamically updates the active
effort when switching providers.
2026-06-18 13:22:29 +02:00
Pierre Tachoire
2b5456ca0b webapi: structured-clone window/MessagePort postMessage messages
window.postMessage and MessagePort.postMessage stashed the message
js.Value.Temp verbatim and handed it straight into the MessageEvent, with
no structuredClone on the path. The receiver got the literal object the
sender posted, so a mutation on either side was visible to the other; worse,
posting to a different same-origin frame exposed the source realm's object
directly into the target realm instead of minting a fresh one there. The
spec requires StructuredSerialize in postMessage and deserialize in the
destination realm.
2026-06-18 12:36:29 +02:00
Pierre Tachoire
80603902be Merge pull request #2570 from lightpanda-io/cache-revalidation
Cache Revalidation
2026-06-18 09:46:02 +00:00
Karl Seguin
4281e0f0f4 Merge pull request #2779 from lightpanda-io/async_tests
tests: Migrate all legacy testing.async(cb) to new testing.async flow
2026-06-18 17:40:23 +08:00
Pierre Tachoire
6726da4293 Merge pull request #2777 from lightpanda-io/ci-agent-alias
ci: upload on s3 an agent alias
2026-06-18 09:37:08 +00:00
Karl Seguin
7cafd05e21 Merge pull request #2767 from lightpanda-io/broadcastchannel
implement BroadcastChannel
2026-06-18 17:26:25 +08:00
Karl Seguin
397176d868 tests: Migrate all legacy testing.async(cb) to new testing.async flow
The legacy testing.async(async() => {...}); was simple to use, but it only
worked in simple cases. Any microtasks which wasn't immediately resolve would
still fail.

The newer testing.async code is more robust and can handle any async scenario.
It's a bit more verbose and it's limited to 1 per <script type=module>. This
commit migrates all remaining legacy usages to the new flow. While the simpler
one was nice, having both approaches is confusing (to humans and ai).
2026-06-18 17:22:20 +08:00
Karl Seguin
31404bf511 Merge pull request #2772 from lightpanda-io/worker_navigator
webapi, worker: Expose Navigator on Worker
2026-06-18 17:13:25 +08:00
Adrià Arrufat
7677fb142a Merge pull request #2778 from lightpanda-io/agent-save-effort-fix
agent: respect configured reasoning effort
2026-06-18 11:08:36 +02:00
Pierre Tachoire
d3ba85818b Merge pull request #2776 from lightpanda-io/remove_external_connection_from_unit_test
tests: remove external connection from unit tests
2026-06-18 08:58:22 +00:00
Adrià Arrufat
275ed43b1e agent: respect configured reasoning effort
Respect the user's configured reasoning effort during save synthesis
and user message processing. This prevents API errors on models that
do not support reasoning effort (like Mistral) when `.none` is set.
2026-06-18 10:53:59 +02:00
Pierre Tachoire
b2f7bdd935 ci: upload on s3 an agent alias 2026-06-18 10:10:59 +02:00
Karl Seguin
723b961be7 tests: remove external connection from unit tests
A unit test was hitting http://example.com/. As far as I can tell, there was no
good reason for this. Replaced it with hitting the local test server and the
test continues to pass.

Three reasons to avoid hitting an external resource in a unit test:
1 - it can be slow
2 - if it's flaky, it's outside our control
3 - since every zig build test generates a new binary, my firewall warns me
    about a new outgoing connection every test
2026-06-18 15:43:37 +08:00
Karl Seguin
314d341169 worker: BroadcastChannel
Expose BroadcastChannel to Worker. This change also enables actual broadcast
messages across frames/workers/poppus on the same origin. The key here is the
introduction of Page.executionsForOrigin(origin) which returns a list of
executions for a given origin. This allows the BroadcastChannel task to iterate
through _all_ candidate broadcast channels for the origin to find targets.

Also changed tests to use new/robust async testing. I'm going to do a follow
up PR to remove all the  "legacy" variants of this.
2026-06-18 15:35:53 +08:00
Adrià Arrufat
01e75268fb Merge pull request #2775 from lightpanda-io/update-zenai
build: bump zenai dependency to change Mistral's default model
2026-06-18 09:31:00 +02:00
Adrià Arrufat
117c254923 build: bump zenai dependency to change Mistral's default model 2026-06-18 09:15:37 +02:00
Pierre Tachoire
f27407294b Merge pull request #2774 from lightpanda-io/nix-update-26.05
Update nixpkgs to 26.05
2026-06-18 06:12:17 +00:00
Muki Kiboigo
148bb7d3b7 update nixpkgs to 26.05 2026-06-17 22:11:48 -07:00
Muki Kiboigo
d891084a8b add internal flag to Request 2026-06-17 20:41:11 -07:00
Karl Seguin
af67a95a2e Merge pull request #2742 from lightpanda-io/window_reuse
uaf, webapi: Window reuse
2026-06-18 11:34:39 +08:00
Muki Kiboigo
6433ca7df2 rename RenewRequest to RenewResponse 2026-06-17 20:32:58 -07:00
Muki Kiboigo
438a5f4320 properly use response headers for renew 2026-06-17 20:30:14 -07:00
Karl Seguin
0b85904967 update v8 dep 2026-06-18 11:19:18 +08:00
Karl Seguin
cecc7d27fd Add global object (window) identity across navigates
Essentially makes it so that:

```
var w = iframe.contentWindow
iframe.src = 'spice.html';
w === iframe.contentWindow
```

Depends on https://github.com/lightpanda-io/zig-v8-fork/pull/182 and leverages
v8's own ability to re-use globals.

This uses v8
2026-06-18 11:16:09 +08:00
Karl Seguin
f94d69a8cd uaf: Window reuse on iframe/popup navigation
This commit re-uses the allocated *Window on frame/popup navigation. It's simple
to understand why this is needed:

```
var opener = window.open('page1.html');
opener.location = 'page2.html'
opener <-- should still be valid
```

What's harder to understand is why this mostly works and why it started to fail.
This mostly works because the *Window is allocated on the page's arena and thus
lives for the duration of the page AND, because we re-use the frame address,
`window._frame` always points to a valid/current Frame (as far as JS is
concerned).

Why is started to fail is because of the move to rust-url (1). On frame.deinit
we now free the underlying window._location._url._url rust-owned memory. So
while the window stays alive and most things work, getting that URL is a UAF.
By re-using the window, we don't change the lifetime of the rust-owned URL, but
we do give the existing window a new location + url. (Which is correct, as
opener.location should reflect the new URL post-navigation).

(1) https://github.com/lightpanda-io/browser/pull/2658
2026-06-18 11:16:08 +08:00
Karl Seguin
ba433059c0 Merge pull request #2764 from lightpanda-io/nikneym/various-crypto-fixes
`SubtleCrypto`: various fixes
2026-06-18 08:48:58 +08:00