Skip to content

JavaScript Evaluation

Execute JavaScript in the browser context and retrieve results.

Basic Evaluation

zig
const std = @import("std");
const cdp = @import("cdp");

pub fn main(init: std.process.Init) !void {
    const allocator = init.gpa;

    var browser = try cdp.Browser.launch(.{
        .headless = .new,
        .allocator = allocator,
        .io = init.io,
    });
    defer browser.close();

    var session = try browser.newPage();
    defer session.detach() catch {};

    var page = cdp.Page.init(session);
    var runtime = cdp.Runtime.init(session);
    
    try page.enable();
    try runtime.enable();

    _ = try page.navigate(allocator, "https://example.com");

    // Wait for load
    var i: u32 = 0;
    while (i < 500000) : (i += 1) {
        std.atomic.spinLoopHint();
    }

    // Simple evaluation
    const result = try runtime.evaluateAs(i64, "1 + 2");
    std.debug.print("Result: {}\n", .{result}); // 3
}

Return Types

String

zig
const title = try runtime.evaluateAs([]const u8, "document.title");
std.debug.print("Title: {s}\n", .{title});

Number

zig
const count = try runtime.evaluateAs(i64, "document.links.length");
std.debug.print("Links: {}\n", .{count});

const ratio = try runtime.evaluateAs(f64, "window.devicePixelRatio");
std.debug.print("DPR: {d}\n", .{ratio});

Boolean

zig
const visible = try runtime.evaluateAs(bool, "document.visibilityState === 'visible'");
std.debug.print("Visible: {}\n", .{visible});

Complex Objects

zig
var result = try runtime.evaluate(allocator,
    \\({
    \\  title: document.title,
    \\  url: location.href,
    \\  links: document.links.length
    \\})
, .{ .return_by_value = true });
defer result.deinit(allocator);

if (result.value) |v| {
    // v is std.json.Value
}

Page Information

zig
// URL
const url = try runtime.evaluateAs([]const u8, "location.href");

// Title
const title = try runtime.evaluateAs([]const u8, "document.title");

// Document ready state
const ready = try runtime.evaluateAs([]const u8, "document.readyState");

// Viewport size
const width = try runtime.evaluateAs(i64, "window.innerWidth");
const height = try runtime.evaluateAs(i64, "window.innerHeight");

// Scroll position
const scroll_x = try runtime.evaluateAs(i64, "window.scrollX");
const scroll_y = try runtime.evaluateAs(i64, "window.scrollY");

DOM Queries

zig
// Element text
const heading = try runtime.evaluateAs(
    []const u8,
    "document.querySelector('h1')?.innerText || 'Not found'",
);

// Element count
const count = try runtime.evaluateAs(
    i64,
    "document.querySelectorAll('.item').length",
);

// Check existence
const exists = try runtime.evaluateAs(
    bool,
    "document.querySelector('#login-form') !== null",
);

// Get attribute
const href = try runtime.evaluateAs(
    []const u8,
    "document.querySelector('a.primary')?.href || ''",
);

Modify Page

zig
// Set value
_ = try runtime.evaluate(allocator,
    "document.querySelector('#search').value = 'hello'",
    .{},
);

// Click button
_ = try runtime.evaluate(allocator,
    "document.querySelector('#submit-btn').click()",
    .{},
);

// Scroll to element
_ = try runtime.evaluate(allocator,
    "document.querySelector('#footer').scrollIntoView()",
    .{},
);

// Add class
_ = try runtime.evaluate(allocator,
    "document.body.classList.add('loaded')",
    .{},
);

Extract Data

zig
var result = try runtime.evaluate(allocator,
    \\Array.from(document.querySelectorAll('a[href]')).map(a => ({
    \\  text: a.innerText.trim(),
    \\  href: a.href
    \\}))
, .{ .return_by_value = true });
defer result.deinit(allocator);

Extract Table

zig
var result = try runtime.evaluate(allocator,
    \\Array.from(document.querySelectorAll('table tr')).map(row =>
    \\  Array.from(row.querySelectorAll('td, th')).map(cell => cell.innerText.trim())
    \\)
, .{ .return_by_value = true });
defer result.deinit(allocator);

Extract Form Data

zig
var result = try runtime.evaluate(allocator,
    \\Object.fromEntries(
    \\  new FormData(document.querySelector('form'))
    \\)
, .{ .return_by_value = true });
defer result.deinit(allocator);

Async Functions

Await Promise

zig
var result = try runtime.evaluate(allocator,
    \\fetch('/api/data').then(r => r.json())
, .{
    .await_promise = true,
    .return_by_value = true,
});
defer result.deinit(allocator);

Custom Async

zig
var result = try runtime.evaluate(allocator,
    \\new Promise(resolve => {
    \\  setTimeout(() => resolve('done'), 1000);
    \\})
, .{ .await_promise = true });
defer result.deinit(allocator);

Error Handling

zig
var result = try runtime.evaluate(allocator,
    "nonExistent.property",
    .{},
);
defer result.deinit(allocator);

if (result.exception_details) |exception| {
    std.debug.print("Error: {s}\n", .{exception.text});
    return;
}

Inject Script

zig
// Add script to run on every new document
const script_id = try page.addScriptToEvaluateOnNewDocument(
    \\Object.defineProperty(navigator, 'webdriver', {
    \\  get: () => false
    \\});
);

// Later: remove if needed
try page.removeScriptToEvaluateOnNewDocument(script_id);

Console API

zig
// Enable console API in evaluation
var result = try runtime.evaluate(allocator,
    \\console.log('Debug message');
    \\$('div') // Like browser console
, .{
    .include_command_line_api = true,
});

Complex Script

zig
const script =
    \\(function() {
    \\  const items = document.querySelectorAll('.product');
    \\  return Array.from(items).map(item => ({
    \\    name: item.querySelector('.name')?.innerText,
    \\    price: parseFloat(item.querySelector('.price')?.innerText.replace('$', '')),
    \\    available: !item.classList.contains('out-of-stock')
    \\  })).filter(p => p.available && p.price < 100);
    \\})()
;

var result = try runtime.evaluate(allocator, script, .{
    .return_by_value = true,
});
defer result.deinit(allocator);

Released under the MIT License.