CDP Protocol
Lightpanda implements the Chrome DevTools Protocol (CDP) to enable browser automation through standard tooling. This means you can use Puppeteer, Playwright, chromedp, or any other CDP-compatible client to control Lightpanda just like you would control Chrome or Chromium.
Parent topic: Architecture
How CDP Works in Lightpanda
When you start Lightpanda in CDP server mode, it opens a WebSocket server that listens for incoming CDP connections. Each connected client sends JSON-encoded CDP commands over the WebSocket, and Lightpanda processes them and returns results in the same format that Chrome would.
The core flow looks like this:
- The client connects to the WebSocket endpoint (e.g.,
ws://127.0.0.1:9222) - The client sends a JSON message specifying a CDP method and parameters
- Lightpanda parses the message, dispatches it to the correct domain handler, and sends back a JSON response
- Asynchronous events (page loads, network activity, console messages) are pushed to the client as CDP events
{
"id": 1,
"method": "Page.navigate",
"params": {
"url": "https://example.com"
}
}
Architecture Overview
The CDP implementation is organized into three layers:
- Server layer (
src/Server.zig) – Manages WebSocket connections, thread spawning, and raw message I/O. - CDP core (
src/cdp/cdp.zig) – Parses incoming JSON messages, routes them to the correct domain, and manages browser contexts and sessions. - Domain handlers (
src/cdp/domains/) – Individual modules that implement specific CDP domains likePage,DOM,Network, andRuntime.
Message Dispatch
When a CDP message arrives, the core parser extracts the domain name from the method string (e.g., Page from Page.navigate). It then uses a compile-time optimized dispatch table that matches domain name lengths and byte values to route the message to the correct handler module. This approach avoids runtime string comparisons entirely.
"Target.createTarget"
| |
domain action
(Target) (createTarget)
Each domain handler receives a Command struct containing the parsed input, the current browser context, and an arena allocator for temporary allocations. The handler processes the command and sends a result or error back through the same WebSocket connection.
Browser Context and Sessions
CDP uses a layered model to manage browser state:
- BrowserContext – An isolated browsing environment (similar to an incognito profile). Lightpanda currently supports one browser context at a time.
- Target – Represents a page within a browser context. Each target has a unique ID.
- Session – A link between the CDP client and a target. The client attaches to a target and receives a
sessionIdused for all subsequent communication with that target.
The typical session lifecycle:
- Client calls
Target.createBrowserContextto create an isolated context - Client calls
Target.createTargetwith a URL to open a page - Lightpanda auto-attaches (or the client explicitly attaches) and returns a
sessionId - All subsequent commands include this
sessionId - Client calls
Target.closeTargetandTarget.disposeBrowserContextwhen done
// Puppeteer example
const browser = await puppeteer.connect({
browserWSEndpoint: 'ws://127.0.0.1:9222'
});
const page = await browser.newPage();
await page.goto('https://example.com');
Startup Behavior
Lightpanda handles an initial “startup” phase for compatibility with automation clients like Puppeteer. Before a real browser context is created, the server responds to a limited set of CDP commands using a dummy session ID (STARTUP). This allows clients that expect pre-existing targets on connection to function correctly.
Supported CDP Domains
Lightpanda implements the following CDP domains. Each domain handles a specific category of browser functionality.
Target
Manages browser contexts, targets (pages), and sessions. This is the entry point for creating and controlling pages.
| Method | Description |
|---|---|
createBrowserContext |
Create an isolated browsing context |
createTarget |
Open a new page at a given URL |
attachToTarget |
Attach to a target and receive a session ID |
closeTarget |
Close a page |
disposeBrowserContext |
Destroy a browser context |
getTargets |
List all active targets |
setAutoAttach |
Automatically attach to new targets |
setDiscoverTargets |
Enable target discovery events |
sendMessageToTarget |
Route a message to a specific target |
Page
Controls page navigation, lifecycle events, and page-level operations.
| Method | Description |
|---|---|
navigate |
Navigate to a URL |
enable |
Enable page domain events |
getFrameTree |
Get the frame tree structure |
setLifecycleEventsEnabled |
Enable/disable lifecycle events |
addScriptToEvaluateOnNewDocument |
Inject script before page load |
createIsolatedWorld |
Create an isolated JavaScript world |
captureScreenshot |
Capture a page screenshot |
getLayoutMetrics |
Get page layout dimensions |
close |
Close the current page |
DOM
Provides access to the document object model for querying and inspecting elements.
| Method | Description |
|---|---|
getDocument |
Get the root DOM node |
querySelector |
Find an element by CSS selector |
querySelectorAll |
Find all elements matching a selector |
performSearch |
Search the DOM by selector |
getSearchResults |
Retrieve search results by index range |
describeNode |
Get detailed node information |
getOuterHTML |
Get a node’s outer HTML |
getBoxModel |
Get a node’s box model dimensions |
resolveNode |
Resolve a node to a JavaScript object |
requestChildNodes |
Request child nodes for a given node |
Runtime
Executes JavaScript in the page context. Most Runtime methods are forwarded to the V8 inspector.
| Method | Description |
|---|---|
evaluate |
Evaluate a JavaScript expression |
callFunctionOn |
Call a function on a remote object |
addBinding |
Expose a function to the page context |
getProperties |
Get properties of a remote object |
releaseObject |
Release a remote object reference |
Network
Monitors and controls network activity, cookies, and HTTP headers.
| Method | Description |
|---|---|
enable / disable |
Enable or disable network event capture |
setExtraHTTPHeaders |
Add custom headers to all requests |
setCookie / setCookies |
Set one or more cookies |
getCookies |
Retrieve current cookies |
deleteCookies |
Delete cookies matching criteria |
getResponseBody |
Get the body of a captured response |
Fetch
Intercepts network requests, allowing you to modify, fulfill, or fail them before they reach the server.
| Method | Description |
|---|---|
enable / disable |
Enable or disable request interception |
continueRequest |
Continue an intercepted request |
failRequest |
Fail an intercepted request |
fulfillRequest |
Fulfill an intercepted request with a custom response |
continueWithAuth |
Continue with authentication credentials |
Input
Simulates user input events such as keyboard and mouse interactions.
| Method | Description |
|---|---|
dispatchKeyEvent |
Simulate a keyboard event |
dispatchMouseEvent |
Simulate a mouse event |
insertText |
Insert text at the current cursor position |
Emulation
Controls device emulation settings. These methods are currently accepted but not fully implemented (noop).
| Method | Description |
|---|---|
setDeviceMetricsOverride |
Override device screen dimensions |
setTouchEmulationEnabled |
Enable or disable touch events |
setEmulatedMedia |
Override media type and features |
Other Domains
The following domains are also implemented with basic support:
- Browser – Browser-level information and version details
- CSS – Computed styles and stylesheet information
- Accessibility – Accessibility tree inspection
- Security – Security origin information
- Performance – Performance metrics
- Storage – Cookie and storage management
- Inspector – Inspector session management
- Log – Console log capture
Node Registry
The CDP implementation maintains a Node.Registry that maps between CDP node IDs (integers) and actual DOM nodes. Whenever a DOM node is sent to the client (e.g., through DOM.getDocument), it is registered with a unique numeric ID. This allows the client to reference specific nodes in subsequent commands like DOM.querySelector or DOM.describeNode.
Client Node Registry DOM Tree
| | |
|-- getDocument ----------->| |
| |-- register(document) ->|
| |<- id: 1 --------------|
|<- { nodeId: 1, ... } ----| |
| | |
|-- querySelector(1, "h1")->| |
| |-- lookup(1) --------->|
| |-- querySelector ------>|
| |<- register(h1) -------|
|<- { nodeId: 2, ... } ----| |
The registry supports bidirectional lookup (ID to node and node to ID) and uses a memory pool for efficient allocation. It is reset between page navigations.
DOM Search
Lightpanda implements the three-step CDP DOM search flow:
DOM.performSearch(query)– Execute a CSS selector query and store the matching node IDsDOM.getSearchResults(searchId, fromIndex, toIndex)– Retrieve a slice of the search resultsDOM.discardSearchResults(searchId)– Release the search resources
Multiple concurrent searches are supported within a single browser context.
Client Integration Examples
Puppeteer
const puppeteer = require('puppeteer');
const browser = await puppeteer.connect({
browserWSEndpoint: 'ws://127.0.0.1:9222'
});
const page = await browser.newPage();
await page.goto('https://example.com');
const title = await page.title();
console.log(title);
await browser.close();
Playwright
const { chromium } = require('playwright');
const browser = await chromium.connectOverCDP('ws://127.0.0.1:9222');
const context = browser.contexts()[0];
const page = await context.newPage();
await page.goto('https://example.com');
const content = await page.textContent('h1');
await browser.close();
chromedp (Go)
ctx, cancel := chromedp.NewRemoteAllocator(
context.Background(),
"ws://127.0.0.1:9222",
)
defer cancel()
ctx, cancel = chromedp.NewContext(ctx)
defer cancel()
var title string
chromedp.Run(ctx,
chromedp.Navigate("https://example.com"),
chromedp.Title(&title),
)
fmt.Println(title)
Memory Management
The CDP layer uses multiple arena allocators to manage memory efficiently:
- Message arena – Temporary allocations for processing a single CDP message. Reset after each message.
- Page arena – Allocations tied to the lifetime of one page navigation. Reset when the page changes.
- Browser context arena – Allocations that persist for the entire browser context lifetime.
- Notification arena – Used for processing asynchronous notifications outside the normal request-response cycle.
This tiered approach ensures that short-lived allocations (like parsing a single command) are cleaned up immediately, while longer-lived state (like the node registry) persists as needed.
Current Limitations
- Only one browser context is supported at a time
- Some Emulation methods are accepted but not yet implemented (noop)
- Screenshot capture returns a placeholder image
- The
Page.addScriptToEvaluateOnNewDocumentmethod stores scripts but execution timing may differ from Chrome
Related Topics
- Architecture – Overall system design and component overview
- Browser Engine – DOM, JavaScript, and page lifecycle internals
- Network Layer – HTTP client and WebSocket implementation details
- CDP Server Quick Start – How to start the server and connect a client