Some WPT tests need to interact with the browser in a way that isn't possible
with web apis. Browsers need to expose a way for tests to do this and then use
the testdriver-vendor.js to hook into these special WPT actions.
This commit sets up the infrastructure for supporting this and includes
the delete_all_cookies functionality needed by various cookie tests (e.g.
/cookies/attributes/attributes-ctl.sub.html).
A new compilation flag, `-Dwpt_extensions`, can be specified. When specified
a `window.webdriver` accessor is defined and a `WebDriver` type is exposed.
Note that, while I only implemented delete_all_cookies for now, I've seen other
tests fail because of missing vendor-specific implementation.
Gives the same treatment to Blob as https://github.com/lightpanda-io/browser/pull/2171
gave to Response.
Creating a blob from JS and creating a blob internally is different enough that
they now share little, and the ergonomics for creating a blob internally are
better / simpler.
Conversely, initializing a Response and Blob from a js.Value now share a
`js_val.toStringSmart()` which can probably be used in other places. Unlike
`toString()` which behaves like JavaScript (e.g. array_buffer.toString() ->
"[object ArrayBuffer]"), `toStringSmart` understands buffers and blobs.
Prunes hidden subtrees from the accessibility tree and implements
accessible name resolution via labels. Adds the `labels` property
to labellable HTML elements.
This commit fixes a few serious issues with the Websocket implementation.
1 - libcurl recursive api calls
Creating a Websocket instance from within a libcurl callback results in libcurl
failing with a RecursiveApiCall error. I fixed this more generally by adding a
`ready_queue` which connections can use when the `HttpClient` is performing
actions. Once `perform` ends, this new `ready_queue` is processed. There might
be a more holistic solution to this (we seem to run into RecursiveApiCall
everywhere), but since HttpClient is going through heavy changes, this seemed
like the smallest possible change to fix it.
2 - "load" blocking
Load and IdleNetwork notifications should not block on Websocket connections. To
solve this, `HttpClient` now ha `http_active` and `ws_active` to replace `active`.
Only `http_active` is used for things like "load" triggering.
3 - The above change made the Runner's job more complicated. It used to be
binary: you either have active connections or not. Now there are different types
of active connections. To keep it simple, and I think probably more correct,
the "done-ness" (based on the `wait` parameter) is now independent of active
(or not) network activity. If the page's `load_state == .complete`, then the
`wait == .done` is considered successful, whether or not we have active
connections.
4 - As a consequence of the above, and seemingly unrelated to all of these
changes, a number of html tests now use the "new" robust async framework. Most
of these tests were using the `testing.onload` (aka `testing.eventually`) which
had somewhat...unclear semantics. These tests passed more of a consequence of
how we processed a page and being very simple (e.g. just needing 1 micro or
macrotask tick). But `eventually` never worked for more complicated cases, and
the previous `testing.async` didn't work well. Now, the test runner waits for
.load (which, as per #3, can fire more aggressively), which caused many
`eventually` tests to fail. Moving these tests to the new `async` is more
robust and works with the new aggressive "load".
We've seen this sort of thing before - an assumption we make about the state of
the DOM through a transition is broken by CustomElement callbacks.
Here we see replaceChild which (a) inserts the new nodes and (b) removes the
old one. As part of removing, our page.removeNode has an assertion that the
child belongs to the parent. This is a guarantee that the Page is asking the
caller (Node.replaceChild in this case) to make. But, if the node being inserted
is a custom element, it can have a callback so step (a) can cause changes to
the document, including removing/moving the node being replaced.
TL;DR - CustomElement callbacks means that we have to check that the child to
be replaced is still a child of the parent after our insert.
Was originally just a []const u8, but now allows for Blob, BufferArray (and
related) and Stream.
I saw this being used on http://www.github.com/ (homepage). Unfortunately its
using the data for webgl stuff that we don't support, but it does move the
processing a step further.
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.
The point of this change is that v8 might call these finalizers after we've
cleared things (e.g. because it's already queued it). We need to make the
FC.Identity self-contained and not rely on any external state (like the
finalizer_callback lookup) which might have new entries.
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>
- Escape braces in help text format string to avoid std.debug.print
interpreting them as format specifiers
- Use writer.interface for std.Io.Writer methods (writeAll, stringify)
instead of calling them directly on fs.File.Writer
- Replace writer.flush() with writer.end() per codebase convention
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
std.io.bufferedWriter doesn't exist in Zig 0.15.2. Use the
file.writer(&buf) pattern that matches the rest of the codebase.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a --cookies-file CLI option that loads cookies from a JSON file
at startup and saves them back on exit. This enables AI agents to
maintain login sessions across multiple Lightpanda invocations.
The cookie format matches CDP Network.Cookie (compatible with
Puppeteer's page.cookies() export):
[{"name":"sid","value":"abc","domain":".example.com","path":"/",
"expires":1234567890,"secure":true,"httpOnly":true}]
Closes#335
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2 small changes:
1 - Ensure that isolated world identity is always reset. Not clear how we can
have identity without a context, but very little harm in doing it this way.
2 - Clear finalizer_callback map _before_ finalizing an instance. Finalizing
an instance could (a) release an arena (they almost all do) and (b) create an
object with the newly released arena. I don't think anything does that now, but
they could. That object could even be passed into v8 during finalization. In
which case, we'd temporarily have 2 "live" instances (one being finalized, one
jsut created) at the same address. Don't think we have any code that does this,
but switching the order (remove from map, then finalize) protects against this
address re-use.
1 big change:
Not really big, but more likely to actually fix things. A finalizer can still
be called _after_ we've cleared the finalizer callback. This can happen if
v8 has queued the finalizer prior to us clearing it. We do see some evidence
that this might be an issue, as many extra releaseRefs are happening in
the message loop or microtasks. This makes that scenario safer. First, it moves
the finalizer identity to a dedicated MemoryPool that can outlive the page.
Second, it uses the finalizer_callback map itself to tell whether or not
anything has to happen.
This is an attempt to fix reference counting issues. When Session.replacePage
is called, isolated worlds survive. This appears to be the correct behavior, but
it means that their identity outlives the page reset, which can result in a
use-after-free. The idea is that, on reset, IsolatedWorld persist, but their
identity is cleared.
It should be impossible for a internal cache get to have an incorrect # of
internal fields. But we're seeing this exact scenario in production.
https://github.com/lightpanda-io/browser/pull/1991 was meant to help with this,
but you can do some pretty weird things in JavaScript and it's possible there's
some combination of JavaScript which still allows calling these methods on a
different receiver.