lightpanda-browser

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:

  1. The client connects to the WebSocket endpoint (e.g., ws://127.0.0.1:9222)
  2. The client sends a JSON message specifying a CDP method and parameters
  3. Lightpanda parses the message, dispatches it to the correct domain handler, and sends back a JSON response
  4. 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:

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:

The typical session lifecycle:

  1. Client calls Target.createBrowserContext to create an isolated context
  2. Client calls Target.createTarget with a URL to open a page
  3. Lightpanda auto-attaches (or the client explicitly attaches) and returns a sessionId
  4. All subsequent commands include this sessionId
  5. Client calls Target.closeTarget and Target.disposeBrowserContext when 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:

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.

Lightpanda implements the three-step CDP DOM search flow:

  1. DOM.performSearch(query) – Execute a CSS selector query and store the matching node IDs
  2. DOM.getSearchResults(searchId, fromIndex, toIndex) – Retrieve a slice of the search results
  3. DOM.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:

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