Commit Graph

117 Commits

Author SHA1 Message Date
Scott Taylor
f7f149c9c0 feat: add LIGHTPANDA_DISABLE_CORE_DUMP to suppress crash core dumps
Lightpanda installs no SIGSEGV handler, so a segfault (or the abort() in
the panic path) falls through to the kernel and writes a core dump. When
many instances run under a shared core_pattern crash reporter -- e.g. a
containerized crawl fleet -- those cores become pure storage/alert noise,
and a browser core can capture the contents of arbitrary pages.

Crashes are already reported via telemetry, so this adds an opt-in
LIGHTPANDA_DISABLE_CORE_DUMP env var (mirroring LIGHTPANDA_DISABLE_TELEMETRY)
that zeroes the soft RLIMIT_CORE at startup. Default behavior is unchanged.
2026-06-30 21:58:05 -04:00
Karl Seguin
01ad869dff Merge pull request #2831 from lightpanda-io/unused_imports
minor: remove unused imports
2026-06-28 07:46:43 +08:00
Halil Durak
a457ddf8a7 Merge pull request #2703 from lightpanda-io/nikneym/rework-url-resolve
`URL`: Rework `resolve` with new URL implementation
2026-06-27 23:56:47 +03:00
Karl Seguin
0aaa77c7e6 minor: remove unused imports 2026-06-27 18:16:31 +08:00
Halil Durak
f6fb653a35 prefer resolveNavigation at page, target and fetch 2026-06-25 18:06:08 +03:00
Halil Durak
7bf68a484d URL: replace ensureEncoded with resolve
Both essentially do the same; we can stick to `resolve` for further IDNA compat.
2026-06-25 18:05:47 +03:00
Karl Seguin
630826160e cli: fetch multiple urls
Modified the fetch command to support multiple URLs. Only allowed when --json
is specified (we can revisit this limitation if anyone asks for it, but for now
it's the only non-ambiguous way to get multiple results).

Probably not a big surprise, but the performance gain comes from being able to
concurrently fetch resources. On fast network, or with caching enabled, the
difference wouldn't be so significant:

```
time begin;
./lightpanda fetch "https://github.com/lightpanda-io/browser/" --json;
./lightpanda fetch "https://www.reddit.com/r/Zig/comments/1p0ur0d/i_started_learning_zig" --json;
end
10.28 secs
```

vs

```
time ./lightpanda fetch --json \
  "https://github.com/lightpanda-io/browser/" \
  "https://www.reddit.com/r/Zig/comments/1p0ur0d/i_started_learning_zig"
5.12 secs
```
2026-06-24 18:16:02 +08:00
Karl Seguin
39046492f1 design: Runner supports multi-pages
Follow up to https://github.com/lightpanda-io/browser/pull/2789 which adds
better multi-page support for runner. When waiting, callers have the choice
to wait for a specific frame, all current frames, or a given list of frames. The
last one, a given list of frames, is the most flexible, allowing callers to
provide an `until` per frame AND receive a per-frame result.

waitForSelector and waitForScript continue to be per frame (which is now
explicitly given).

Like 2789, while the changes aren't insignificant (Runner is doing important
work), a number of files were touched either purely because of changes in tests
or other superficial changes, e.g. `session.runner` is now infallible.
2026-06-24 17:44:58 +08:00
Karl Seguin
da6cd66e36 design: multi-page session
Replaces the 1 (+1 inflight) page design of session for an unlimited number of
pages. The main goal is to support a more efficient async goto (1). Without this
commit, async goto has two implementation: multiple browser, which is resource
intensive (thread + isolate per page), bolting it onto sub-frames (like iframe
or popups). The issue with the 2nd version (which I originally suggested) is
that most resources are tied to the Page. So even if an async goto "page" (which
would map to a Frame) is released, most things stay in memory, including the
DOM (page.factory) and the V8::Context.

This new approach adds multiple page support to sessions. The advantage is
pretty clear: the existing memory model (page-tied resources) becomes a strength
of the design, rather than a weakness.

This change is not tirivla, but the diff is inflated by 2 large mechanial
changes, so it isn't _that_ big either. That said, I'd divide this into four
parts.

1 - The old concept of _active/_pending is now baked into the Page itself. A
Page has a `replaces: ?*Page = null` and `replacement: ?*Page = null` field.

2 - Because of #1 above, the Session now just has an `pages: ArrayList(*Page)`.
As much as possible, single-page APIs, like `removePage` no longer exists. We're
trying to present a consistent multi-page API. See #3.

3 - References to *Page and *Frame have always been dangerous. For example,
`lightpanda.fetch` has a block to scope `frame`:

```zig
{
    const frame = try session.createPage();
    // frame isn't safe to use after navigate, it can be swapped out
    _ = try frame.navigate(...)
}
```

While we still hand out *Page and *Frame (user-beware), some of the more
important APIs now take a frame_id which Session can resolve. Furthermore,
createPage now returns a PageHandle which is a safe wrapper around a page/frame.

4 - Two large mechanical changes were made:
  a - Many tests were superficially changed to account for new naming or use
      a new test-helper to preserve the single-page illusion (because 99% of
      tests _are_ single-page)
  b - Many CDP changes where `bc.session.currentFrame()` -> `bc.mainFrame()`).
      This isn't to say CDP changes are meaningless, but it's mostly 1 change
      about how the "main" target/frame_id is tracked (by CDP itself, rather
      than the Session) that required a number of superficial changes to
      accomodate

`Runner` remains largely single-page focused. Runner and some MCP/Agent tools
continue to be tied to the "currentFrame" or "primaryPage". The Runner is
something I want to address in a follow-up PR, but we need to figure out what
it means to "wait" for multiple pages.
As much as possible, Session becomes multi-page native and has no concept of
a special/first/primary/main page. Users of session become responsible for
tracking pages of interest.

This change is not trivial, but the diff is inflated by 2 large mechanical
changes:
1 - Many tests

(1) https://github.com/lightpanda-io/browser/pull/2759
2026-06-24 07:01:29 +08:00
Adrià Arrufat
4147274fa8 Merge branch 'main' into agent 2026-06-14 10:28:21 +02:00
Karl Seguin
4dbec74dfc debug: Use normal crash report assertion in debug
I don't know why I thought debug should `unreahcable` while release should
generate a crash report. The crash report always contains more info and whenever
I encounter a failed assertion in debug, the first thing I do is remove the
unreachable to get the more complete crash report.

(The crash report path skips sending the crash report in debug builds already)
2026-06-13 17:59:54 +08:00
Adrià Arrufat
88c8828224 Merge branch 'main' into agent 2026-06-05 12:43:46 +02:00
Adrià Arrufat
ac82551e66 agent: replace -i flag with /save and /load commands
Removes the `-i`/`--interactive` CLI flag and live file-based
recording. Instead, the REPL now supports a `/load <path>` command
to run scripts from disk, and `/save` to export the in-memory
session recording.

The `Recorder` is simplified to be purely in-memory, and the script
runtime is moved to `src/script/Runtime.zig`.

BREAKING CHANGE: The `-i`/`--interactive` flag has been removed. Use
the `/save` and `/load` commands within the REPL instead.
2026-06-03 16:21:10 +02:00
Karl Seguin
1e2e283c0c Add canary to RC
We still occasionally see release overflow errors. I think this is a UAF, since
I cannot find any mismatched acquire/releases. So this adds a canary value to
every RC and includes it in the crash dump.

If the canary value is different than what we set it at, then we have a UAF.
If the canary value is set to the poison we set on release, then we have a
double free.
If the canary value is unchanged, then it's inconclusive.
2026-06-03 18:42:24 +08:00
Adrià Arrufat
044b34d228 refactor: remove legacy PandaScript self-healing and execution
Removes the `--self-heal` CLI option, the `scriptStep` and `scriptHeal`
MCP tools, and associated verification/iterator machinery. Replaces
"PandaScript" terminology with "slash commands" and moves shared
helpers to `tools.zig`.

BREAKING CHANGE: The `--self-heal` CLI flag and the `scriptStep` and
`scriptHeal` MCP tools have been removed.
2026-06-01 17:29:24 +02:00
Francis Bouvier
8824174afa agent: run recorded scripts as JavaScript
Replace recorded agent replay with a standalone JavaScript script runtime.
Install synchronous agent primitives in an isolated V8 context, add console
output, return structured extract values as JS objects/arrays, and route script
execution through the new runtime.

Update recording to emit .js function calls, default /save filenames to .js,
drop script-level extract save support, and refresh agent docs/tutorials for
the new format.

Self-healing is disabled for now.
2026-06-01 15:43:37 +02:00
Karl Seguin
e6332ac121 Close session before freeing notification
With the new CookieStore, the session must be freed before the notification is.
This is how it works in CDP, but in fetch, we were pretty lazy about it. This
caused the notification to be freed first, and then the cookiestore to try to
unregister: UAF.
2026-05-30 08:44:35 +08:00
Adrià Arrufat
4bc5baf7ee agent: improve command diagnostics and fix various bugs
- Add diagnostics to command parsing to report invalid field values.
- Fix UTF-8 truncation in spinner to avoid splitting codepoints.
- Fix swapped arguments in log error formatting.
- Distinguish "null" values from missing elements in verifier.
- Handle MCP tool cancellation and timeouts with specific error codes.
2026-05-25 17:51:46 +02:00
Adrià Arrufat
989932b40a Merge branch 'main' into agent 2026-05-20 14:57:32 +02:00
Karl Seguin
e7b16983bb Add help documentation + small tweaks
This adds help documentation for the --json flag. This is the only thing that
must be kept from this commit.

It uses our testing.zig to streamline the json testing (instead of string
probing). It removes the JsonEnvelope in favor of an anonymous struct (though,
I'm fine with adding it back in if it's needed to resolve something ambiguous).

Finally, I removed the last unit test as, at that point, it's really just
testing Zig's JSON stringifier (could arguably make the same case for the other
two, but there's some logic there about how nulls/empty might be handled).
2026-05-20 10:44:41 +08:00
Marc Helbling
fec2bbda7b review 2026-05-19 18:13:34 +02:00
Marc Helbling
a89a28a4a2 feat: add --json to fetch command
The `fetch` command is very practical to render pages without needing to
have a long running browser instance.
It is however masking all details on the fetch, most importantly the HTTP status code.
This is a big caveat when leveraging `lightpanda fetch` in a pipeline.

This introduces a `--json` option to provide a structured output that
contains:
* url
* HTTP status code
* response headers
* rendered content as controlled by the `--dump` option

The proposal is to always output the same JSON format even when not
using `--dump` with an option.
2026-05-19 12:08:23 +02:00
Adrià Arrufat
d0a8da453b agent: implement graceful Ctrl-C interruption 2026-05-15 18:45:56 +02:00
Adrià Arrufat
eb1986b8eb Merge branch 'main' into agent 2026-05-15 10:51:45 +02:00
Karl Seguin
a59ddeb360 Dump using the latest Frame to prevent segfault during on frame change
Fixes: https://github.com/lightpanda-io/browser/issues/2446
2026-05-14 20:00:20 +08:00
Adrià Arrufat
70edee7063 lightpanda: close session before notification deinit 2026-05-13 17:49:00 +02:00
Adrià Arrufat
c41955ade3 script: move script module to root src directory 2026-05-11 19:56:36 +02:00
Adrià Arrufat
1a84f56160 refactor: relocate PandaScript and improve agent reliability
Moves script logic to `browser/script/` for shared use. Implements
message rollback on API failure, caches environment variables, and
fixes a potential panic in the spinner.

- Relocate Command, Recorder, and Verifier to `src/browser/script/`
- Implement message rollback on API and synthesis failures in Agent
- Cache `LP_*` environment variables process-wide with mutex protection
- Fix potential panic in Spinner during backward clock jumps
- Improve Recorder to handle write failures and multi-line comments
- Update documentation regarding attachments and path safety
2026-05-11 19:49:15 +02:00
Adrià Arrufat
75b7a8ec6d Merge branch 'main' into agent 2026-05-11 17:52:35 +02:00
Halil Durak
246f91d1f8 --inject-script: prefer splice bytes into <head> directly 2026-05-11 15:15:35 +03:00
Halil Durak
c566d0c41c introduce --inject-script and --inject-script-file
* Prefer `--inject-*` prefix.
* Support injecting multiple scripts (also allows using both variants together).
* Instead of executing scripts in JS context, actually insert them to `<head>` for correct dump output.
2026-05-11 15:15:35 +03:00
Halil Durak
39f12a5669 fetch: add support for --script option
Allows passing a JS file as an arg to be executed alongside other scripts.
2026-05-11 15:15:35 +03:00
Adrià Arrufat
c6ccd83ac4 mcp: add pandascript recording and self-healing tools
Adds tools to record sessions and heal scripts over MCP. Refactors
shared logic to `script.zig` and adds a TTY spinner for the agent.
2026-05-07 20:11:40 +02:00
Adrià Arrufat
a00161f54c Merge branch 'main' into agent 2026-05-05 08:05:57 +02:00
Nikolay Govorov
9a312a4177 Refactor server/client/cdp structure 2026-05-04 16:41:22 +01:00
Adrià Arrufat
1d7e92beeb Merge branch 'main' into agent 2026-04-27 06:40:15 +02:00
Karl Seguin
12c2efb811 Adds --terminate-ms command line argument + ctrl-c improvements in fetch
The main.zig path for `fetch` now captures the *Browser so that
browser.env.terminate() can be called. This is a bit more complex than the serve
path because the Browser owns the Isolate and can't be moved from one thread to
another.

With main having access to the browser, two things are now possible:
1 - We can support a --terminate-ms flag (https://github.com/lightpanda-io/browser/issues/2206)
2 - ctrl-c can correctly stop blocked JavaScript processes

1 is implemented via setitimer to set a timer for SIGALRM, avoiding the need to
add another "watcher" thread, or putting a timer in Network.run.
2026-04-25 12:34:06 +08:00
Adrià Arrufat
ec3ff945cd Merge branch 'main' into agent 2026-04-23 12:31:24 +02:00
Karl Seguin
550fb58f3f Introduce Page (container)
Follow up to https://github.com/lightpanda-io/browser/pull/2200

This change is actually pretty mundane, but a bunch of files that used to
take a *Session (e.g. every WebAPI releaseRef and deinit) now take a *Page.

This aims to separate the 2 lifetimes currently managed by Session by moving
the "Page" lifetime to a dedicated container: Page. Ultimately, the goal is to
remove the 1-page-per-session limit of the current design. Not to explicitly
support multiple pages per session (though, that's more possible now), but
in order to better emulate Chrome where, during a navigation event, the old and
new page both exist.
2026-04-23 15:48:13 +08:00
Adrià Arrufat
58308cfdca Merge branch 'main' into agent 2026-04-22 07:13:04 +02:00
Karl Seguin
2275416505 Page -> Frame
This is to pave the way for introducing a new "Page" container, which will take
over the page lifecycle currently burdening Session. The ultimate goal of that
is to allow the Session to have multiple pages (mostly for better transitions
between pages), which is hard to do now since the Session has so much state.

This rename was aggressive, e.g. currentPage() -> currentFrame() so that, when
the new Page container is added, you won't see "currentPage()" and wonder:

  "Does 'currentPage' mean the new Page container, or the Frame (which
  used to be called Page)".
2026-04-22 08:42:18 +08:00
Adrià Arrufat
efc2b5d236 Merge branch 'main' into agent 2026-04-20 09:37:36 +02:00
Karl Seguin
2d20e57f80 Change all @import("...../log.zig") to const log = lp.log;
@import("lightpanda") where needed.

Would also like to do this for String, Page, Session and js which all stand out
as types that are use across the codebase.

I know that a few devs are doing this in new work and I haven't heard anyone
voice an objection.
2026-04-20 12:40:04 +08:00
Adrià Arrufat
2d808336df Merge branch 'main' into agent 2026-04-18 08:28:03 +02:00
Karl Seguin
e6a190c72d Improve Cookie parsing rules
This commit was focused on making small changes to cookie parsing in order to
improve a handful of WPT cases.

Protects against nameless cookies that have a value with certain prefixes (e.g.
`__Secure-`) which could be pased into an incorrect prefix.

It allows tabs in cookie names/values.

It enforces that certain prefix have certain settings.

Along the way:
- Added window.origin
- Ensure URL passed to Request is escaped
- Added log on fetch error
- made --dump wpt only list non-passing results
2026-04-17 19:12:19 +08:00
Adrià Arrufat
49e4ed6fc2 Merge branch 'main' into agent 2026-04-17 09:19:46 +02:00
Karl Seguin
3ca1f230b9 Serialize sameSite
Tweak ergonomics (public functions log internally and are infallible). Use
readFileAlloc directly. Fix possible memory leak with cookie arena - I don't
think you can make a copy of the arena, and then dupe with the original.
2026-04-16 10:35:34 +08:00
Pierre Tachoire
a24fcc6a5c use session arg to load cookies from file 2026-04-15 10:29:53 -04:00
Pierre Tachoire
cc4bd417d2 save cookies at the end of fetch 2026-04-15 10:09:14 -04:00
Matt Van Horn
35991a1b32 refactor: split --cookies-file into --cookie/--cookie-jar per curl convention
Split the single --cookies-file flag into two flags following curl's
convention as requested by @krichprollsch:

- --cookie (read-only): loads cookies at startup for fetch, mcp, and
  serve/CDP commands
- --cookie-jar (write-only): saves cookies on exit for fetch and mcp
  only (CDP cookie-jar deferred per maintainer guidance)

Add cookie integration to MCP server (load in init, save in deinit)
and CDP session creation (load only). The serve command now rejects
--cookie-jar with a clear error message.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 10:09:13 -04:00