Documentation
¶
Overview ¶
Package chrome drives a real Chrome through the Chrome DevTools Protocol and implements browser.Driver.
It is a module of its own — github.com/mind-vm/agentloop/browser/chrome — because chromedp and cdproto together are larger than the whole of agentloop, and a deployment that never opens a browser should not carry them. A replace directive keeps it buildable from a checkout; consumers opt in with one `go get`.
The split is also what contains the toolchain floor: cdproto reaches a package that declares a newer Go than agentloop itself asks for, so this module needs Go 1.26 where the root module needs 1.25. Keeping it a separate module means that requirement never reaches anyone who does not build a browser driver.
chrome := chrome.New(chrome.Options{})
defer chrome.Close()
pack := browser.Pack(ctx, chrome, nil)
Why CDP directly ¶
CDP is the protocol Chrome DevTools itself speaks, and every automation library — Playwright, Puppeteer, Selenium's CDP mode — ends up speaking it. chromedp is a Go client for it, so this is one Go dependency rather than a Node helper process driven over a second hop.
One page, one process ¶
A Chrome is one browser process, one page, and one temporary profile, which is what keeps sessions from seeing each other's cookies. That isolation is the expensive kind: an idle headless Chrome is 4-6 OS processes and ~100 MB, a content page 150-300 MB, a heavy SPA 400-700 MB, so concurrency should be planned against measured memory rather than session count, and the process-count pressure (400-600 processes for 100 sessions) tends to hit file-descriptor and pid limits before RAM.
Where sessions share a trust boundary — the same org, or an application's own internal automations — several tabs in one browser cost ~30-50 MB each instead, at the price of a shared crash blast radius. That variant is not implemented here: it belongs to whatever pools browsers, not to a single-page driver.
Index ¶
- type Chrome
- func (c *Chrome) Click(ctx context.Context, selector string) error
- func (c *Chrome) Close()
- func (c *Chrome) Eval(ctx context.Context, expr string, out any) error
- func (c *Chrome) KeyEvent(ctx context.Context, text string) error
- func (c *Chrome) Navigate(ctx context.Context, url string) error
- func (c *Chrome) Screenshot(ctx context.Context) ([]byte, error)
- func (c *Chrome) SendKeys(ctx context.Context, selector, text string) error
- func (c *Chrome) Text(ctx context.Context, selector string) (string, error)
- func (c *Chrome) Value(ctx context.Context, selector string) (string, error)
- func (c *Chrome) WaitReady(ctx context.Context, selector string) error
- func (c *Chrome) WaitVisible(ctx context.Context, selector string) error
- type Options
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Chrome ¶
type Chrome struct {
// contains filtered or unexported fields
}
Chrome drives one page in one Chrome process through the Chrome DevTools Protocol, and implements browser.Driver.
The process starts lazily on the first call and lives until Close, so constructing one per session is cheap for the sessions that never browse. Calls are serialised: a Chrome is one page, and two overlapping navigations on one page are a race whatever the caller intended.
Callers must Close it. A per-session Chrome belongs to whatever owns the session (an agentloop SandboxBuilder returns a cleanup func for exactly this); a shared one belongs to the process.
func (*Chrome) Close ¶
func (c *Chrome) Close()
Close shuts the browser down and releases its temporary profile. Safe to call more than once, and on a Chrome that never started.
func (*Chrome) Eval ¶
Eval implements browser.Driver. A nil out discards the result, which is not just an optimisation: chromedp rejects an undefined completion value when a destination is given, and the void half of the Set-of-Marks API returns exactly that.
func (*Chrome) Screenshot ¶
Screenshot implements browser.Driver. The result is PNG: screencast frames are JPEG because they are for watching, but a still that a vision model has to read text off is worth the extra bytes.
type Options ¶
type Options struct {
// ExecPath is the Chrome/Chromium binary. Empty lets chromedp find
// one the usual way (PATH, then the platform's standard install
// locations).
ExecPath string
// Headed runs Chrome with a visible window. Off by default —
// headless is what a server wants, and a visible window on a
// developer's machine steals focus on every navigation.
Headed bool
// Width and Height size the viewport, in CSS pixels. Zero uses
// 1280x800. The viewport matters more than usual here: mark() only
// enumerates what is currently visible, so a short viewport means
// more scrolling and more re-marking.
Width, Height int
// UserDataDir is Chrome's profile directory. Empty gives each
// Chrome a fresh temporary profile that is deleted on Close —
// which is what isolates one session's cookies from another's.
// Point two Chromes at the same directory and they will fight.
UserDataDir string
// Timeout caps a single driver call. Zero uses 30s. It is a
// backstop, not the mechanism for ending a turn: the per-run
// context each call carries does that, and fires first.
Timeout time.Duration
// Flags are extra Chrome command-line flags, applied after the
// defaults so they win. A false bool omits the flag entirely.
Flags map[string]any
// OnFrame, when set, subscribes to Chrome's screencast: it is
// called with a base64-encoded JPEG each time the viewport changes,
// which is what a live view of a headless browser is made of.
// Chrome pushes a frame only when something actually changed and
// waits for each acknowledgement before sending the next, so an
// idle page costs nothing and a slow consumer drops frames rather
// than building a backlog.
//
// It is called from Chrome's event goroutine: return quickly, and
// do not call back into the driver from it.
OnFrame func(jpegBase64 string)
}
Options configures a Chrome. The zero value is the one most callers want: a headless 1280x800 Chrome from PATH, with a 30-second cap on any single step.