During a root navigation, we keep the existing page active until we get
the headers callback from the pending page. Then
Session.commitPendingPage makes the switch.
It delays the deinit of CPD execution context to handle JS execution in
the meantime.
Now session has an array of two pages, _active_idx points to the main
page.
Both active and pending pages share the same frame_id, it must remains
stable. So this PR adds a Request.protect_from_abort to avoid removing
the request form the pending page when deinit the previous active page.
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.
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)".
@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.
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.
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>
- Extract dispatchInputAndChangeEvents() in actions.zig, used by fill,
selectOption, and setChecked
- Extract resolveNodeAndPage() in tools.zig, used by click, fill, hover,
selectOption, setChecked, and nodeDetails handlers
- fill action now calls focus() on the element before setting its value,
ensuring focus/focusin events fire for JS listeners
- Add findElement MCP tool for locating interactive elements by ARIA role
and/or accessible name (case-insensitive substring match)
- Add tests for findElement (by role, by name, no matches, missing params)
- Fix setChecked event order: click fires before input/change to match
browser behavior
- Add tests for hover, press, selectOption, setChecked MCP tools
- Merge all action tests into a single test case sharing one page load
- Add test elements to mcp_actions.html (hover target, key input,
second select, checkbox, radio)
New browser actions and MCP tools for AI agent interaction:
- hover: dispatches mouseover/mouseenter events on an element
- press: dispatches keydown/keyup keyboard events (Enter, Tab, etc.)
- selectOption: selects a dropdown option by value with input/change events
- setChecked: checks/unchecks checkbox or radio with input/change/click events
- Removed unused ImageContent from protocol.
- Simplified CallToolResult back to only support TextContent.
- Cleaned up CallToolResult usages in tools.zig.
- Fixed CDP inactivity timeout by resetting it when the browser is busy (loading or executing macrotasks).
- Removed the placeholder screenshot tool.
- Refactored MCP tool schemas to constants to avoid duplication.
- Configurable navigation timeouts and wait strategies in MCP tools.
- Default navigation timeout increased from 2s to 10s.
- Added navigate, eval, and screenshot MCP tools.
- Supported running a CDP server alongside MCP using --cdp-port.
- Fixed various startup crashes when running CDP in MCP mode.
- Hardened MCP server error handling.
- Handle failures during HTML, Markdown, and link serialization.
- Return MCP internal errors when result serialization fails.
- Refactor resource reading logic for better clarity and consistency.
This fixes a bug in MCP where interactive elements were not assigned
a backendNodeId, preventing agents from clicking or filling them. Also
extracts link collection to a shared browser module.
Address review feedback from @karlseguin:
1. Use Form.getElements() instead of manual TreeWalker for field
collection. This reuses NodeLive(.form) which handles fields
outside the <form> via the form="id" attribute per spec.
2. Add disabled detection: checks both the element's disabled
attribute and ancestor <fieldset disabled> (with first-legend
exemption per spec). Fields are flagged rather than excluded -
agents need visibility into disabled state.
3. allocator is now the first parameter in collectForms/helpers.
4. handleDetectForms returns InvalidParams on bad input instead
of silently swallowing parse errors.
5. Added tests for disabled fields, disabled fieldsets, and
external form fields via form="id".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This is done for a couple reasons. The first is just to have things a little
more self-contained for eventually supporting more advanced "wait" logic, e.g.
waiting for a selector.
The other is to provide callers with more fine-grained controlled. Specifically
the ability to manually "tick", so that they can [presumably] do something
after every tick. This is needed by the test runner to support more advanced
cases (cases that need to test beyond 'load') and it also improves (and fixes
potential use-after-free, the lp.waitForSelector)
Add a detectForms MCP tool and lp.detectForms CDP command that return
structured form metadata from the current page. Each form includes its
action URL, HTTP method, and fields with names, types, required status,
values, select options, and backendNodeIds for use with the fill tool.
This lets AI agents discover and fill forms in a single step instead of
calling interactiveElements, filtering for form fields, and guessing
which fields belong to which form.
New files:
- src/browser/forms.zig: FormInfo/FormField structs, collectForms()
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After click, fill, and scroll actions, return the current page URL
and title instead of static success messages. This gives AI agents
immediate feedback about the page state after an action, matching
the pattern already used by waitForSelector.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add three test cases covering:
- Immediate match on an already-present element
- Polling match on an element added after a 200ms setTimeout delay
- Timeout error on a non-existent element with a short timeout
Add mcp_wait_for_selector.html test fixture that injects a #delayed
element after 200ms via setTimeout for the polling test.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a waitForSelector tool to the MCP server that polls for a CSS
selector match with a configurable timeout (default 5000ms). Returns the
backendNodeId of the matched element for use with click/fill tools.
The tool runs the session event loop between selector checks, so
dynamically-created elements are found as they appear from JS execution
or network responses.