chrome

package module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 29, 2026 License: MIT Imports: 7 Imported by: 0

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

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 New

func New(opts Options) *Chrome

New returns a Chrome that will start on its first use.

func (*Chrome) Click

func (c *Chrome) Click(ctx context.Context, selector string) error

Click implements browser.Driver.

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

func (c *Chrome) Eval(ctx context.Context, expr string, out any) error

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) KeyEvent

func (c *Chrome) KeyEvent(ctx context.Context, text string) error

KeyEvent implements browser.Driver.

func (*Chrome) Navigate

func (c *Chrome) Navigate(ctx context.Context, url string) error

Navigate implements browser.Driver.

func (*Chrome) Screenshot

func (c *Chrome) Screenshot(ctx context.Context) ([]byte, error)

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.

func (*Chrome) SendKeys

func (c *Chrome) SendKeys(ctx context.Context, selector, text string) error

SendKeys implements browser.Driver.

func (*Chrome) Text

func (c *Chrome) Text(ctx context.Context, selector string) (string, error)

Text implements browser.Driver.

func (*Chrome) Value

func (c *Chrome) Value(ctx context.Context, selector string) (string, error)

Value implements browser.Driver.

func (*Chrome) WaitReady

func (c *Chrome) WaitReady(ctx context.Context, selector string) error

WaitReady implements browser.Driver.

func (*Chrome) WaitVisible

func (c *Chrome) WaitVisible(ctx context.Context, selector string) error

WaitVisible implements browser.Driver.

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL