默认 LayoutPolicy::Mock 不执行真实布局,截图和坐标输入需显式加 --layout;图片、字体、音视频等资源默认不抓取,需加 --resource 或对应开关;moli serve 默认监听 127.0.0.1:9222,Playwright 通过 connectOverCDP 连接该地址。
moli
lexmountApache-2.0活跃维护用 Rust 打造的无头浏览器,主打轻量、快速、高兼容,专为 AI Agent 的网页操作与自动化任务设计。
Best headless browser for AI agents. Lite, Fast, High-Compatibility. Built in Rust
curl --proto '=https' --tlsv1.2 -fsSL https://github.com/lexmount/moli/releases/latest/download/moli-installer.sh | shREADME 未说明具体显存或 CPU 要求;支持 Linux、macOS、Windows,渲染为 CPU 软件渲染,未提及 GPU 厂商或型号。
面向 AI Agent 的 Rust 无头浏览器,按需布局渲染,兼顾完整运行时与轻量资源占用
用 Rust 打造的无头浏览器,主打轻量、快速、高兼容,专为 AI Agent 的网页操作与自动化任务设计。
核心能力
- 默认只读 DOM 与样式状态,跳过布局和绘制
- 加 --layout 才启用真实布局、命中测试与截图
- CLI 直接输出 Markdown、语义树、HTML、JSON
- 同一端口同时提供 CDP、WebDriver Classic 和 BiDi
- 内置 V8、Stylo、Taffy 等完整浏览器内核组件
容易踩的坑
- v1.1.23Moli v1.1.23最新正式版2026-11-09 · 2026-11-09 17:24 UTC
What's Changed
WebAudio & Media
- fix(renderer): unblock Feishin media player initialization #2468
- fix(media): complete audio constructors and native media ownership #2479
- feat(webaudio): expose native scheduled audio sources #2517
- feat(webaudio): implement native DelayNode and StereoPannerNode #2518
- feat(webaudio): implement channel routing nodes and port controls #2519
- feat(webaudio): add native AudioBuffer storage and channel bindings #2520
- feat(webaudio): implement WaveShaperNode controls and backend guards #2521
- feat(webaudio): implement ConvolverNode impulse response controls #2522
- feat(webaudio): implement IIRFilterNode and analytic frequency responses #2523
- feat(webaudio): implement PeriodicWave and native oscillator controls #2524
- fix(webaudio): support bounded OfflineAudioContext overloads #2525
Performance, Resource Timing & Markdown
- fix(images): skip duplicate timing entries for decoded image reuse #2452
- feat(performance): expose native Resource Timing initiatorUrl snapshots #2445
- fix(performance): retain classic script initiators for XHR timing #2446
- fix(performance): retain Window callback registration initiators #2447
- fix(performance): retain DOM listener registration initiators #2448
- fix(performance): snapshot event handler assignment initiators #2449
- fix(performance): retain traced and observer callback initiators #2450
- fix(performance): retain simple EventTarget callback initiators #2451
- perf(markdown): avoid repeated ancestor scans during export #2477
Fetch, Networking & Service Workers
- fix(websocket): truncate fractional CloseInfo codes before validation #2460
- fix(fetch): accept the Font resourceType in Fetch.enable patterns by @yepbro in #2470
- fix(fetch): intercept font and media loads with shared admission #2476
- fix(fetch): preserve cache mode across request continuation #2582
- refactor(fetch): share resource admission and simplify diagnostic errors #2591
- refactor(fetch): share callback child handle decoding #2594
- refactor(network): share crossorigin request mode selection #2596
- refactor(fetch): share policy rejection recording #2616
- refactor(xhr): share policy failure recording and delivery #2617
- fix(service-workers): query clients by storage key and visibility #2568
- fix(service-workers): select the matching registration before its controller #2560
- refactor(service-workers): share functional event finalization #2573
- refactor(service-workers): share Window and Worker argument conversion #2614
- refactor(transport): share header memory accounting #2631
CDP, Inspector & Protocol
- fix(cdp): report JavaScript dialogs to every attached Page session by @BibekPathak in #2341
- fix(inspector): avoid hanging eval on lone UTF-16 surrogates #2478
- refactor(bidi): share channel message envelopes #2608
- refactor(protocol): share client window wire projection #2607
- refactor(webdriver): share Classic field value conversion #2606
- refactor(protocol): share BiDi user context resolution #2602
- refactor(bidi): share string field conversion #2605
- refactor(protocol): share console message level projection #2604
- refactor(protocol): share intercepted request identity recording #2603
- refactor(protocol): share RemoteObject value projection #2601
- refactor(protocol): share nonzero inspector id allocation #2626
- refactor(protocol): share worker runtime error mapping #2629
- refactor(protocol): share intercept stage fallback selection #2630
Renderer, Navigation & DOM
- fix(popups): execute noopener responses through one browser-owned Page #2433
- fix(renderer): synchronize cross-origin child Windows internally #2372
- feat(dom): retain source response status on Document identities #2484
- fix(idle): wait for pending javascript navigation handoffs #2465
- fix(dom): reuse XML fragment serialization for XML innerHTML #2569
- refactor(navigation): share navigat
- v1.1.22Moli v1.1.222026-11-06 · 2026-11-06 15:05 UTC
查看更新说明
What's Changed
Markdown & DOM Export
- fix(renderer): export live rendered content through shared markdown conversion by @lanyue-llk in #1337
- fix(markdown): export composed Shadow DOM content #2376
- perf(markdown): export live DOM with lazy projection caches #2402
Fetch, XHR & File API
- fix(xhr): rewrite charsets only for UTF-8 text request bodies #2258
- fix(xhr): serialize native HTML and XML Document bodies #2269
- fix(file-api): run FileReader phases as native tasks #1686
- fix(fetch): preserve and clone Request stream bodies #1687
- fix(file-api): schedule Blob reads on native file-reading tasks #1688
- fix(fetch): consume bodies on native receiver networking tasks #1689
- fix(fetch): read constructed Blob bodies on file-reading tasks #1690
- fix(fetch): transfer Request bodies through native streams #1691
- fix(fetch): tee Response bodies for every public source #1692
- fix(file-api): convert reader inputs before read algorithms #1710
- fix(fetch): complete child frame document interception by @erenkucuker in #2289
- feat(fetch): intercept child document responses and auth challenges #2405
HTTP Cache, Headers & Service Workers
- fix(cache): preserve response stream rejection identity #2381
- fix(fetch): apply cache-mode request controls without replacing author fields #2299
- fix(fetch): prepare Accept before ServiceWorker and network dispatch #2378
- fix(fetch): admit stale cache reuse and reject cache-only network misses #2301
- fix(fetch): preserve Window HTTP cache modes through native dispatch #2302
- fix(service-worker): expose soft reload navigation cache policy #2382
- fix(service-worker): enforce Page policy on network fallback #2394
- fix(worker): preserve Fetch cache modes and conditional-header policy #2303
- fix(service-worker): preserve streamed response completion order #2412
- fix(service-worker): reject requests from closed Window owners #2416
CDP, Navigation & Protocol
- fix(protocol): preserve popup navigation before initial Page adoption #2360
- fix(protocol): align browser version discovery metadata #2366
- fix(cdp): preserve document.open loads for late observers #2379
- refactor(cdp): decouple load publication from lifecycle observers #2391
- refactor(cdp): simplify load publication state #2401
- fix(runtime): follow navigation that supersedes a lifecycle wait #2390
- refactor(navigation): share document request execution #2393
- fix(navigation): retain live Page ownership when replacement observers cancel #2417
Renderer & Runtime
- fix(renderer): reuse named inspector worlds across frame lifetimes #1642
- perf(renderer): defer scriptless document main-world initialization #2409
- fix(renderer): align inline handler invalidation and retained Window clocks #2413
- fix(renderer): avoid panic during streaming parser task publication #2440
DOM, HTML & Forms
- fix(dom): restore legacy createEvent interface semantics #2380
- fix(dom): allocate Document.createRange results in the Document realm #892
- fix(dom): enforce active Document location access and href forwarding #2431
- fix(dom): materialize unforgeable location on every Document wrapper #2437
- refactor(dom): remove detached Node private-state mirroring #2407
- fix(dom): align XML namespace serialization #482
- fix(dom): preserve ordinary xmlns attribute namespace identity #2384
- fix(forms): reset parser-associated owners at native mutation boundaries #2427
- fix(forms): retain image parser and ancestor form owners #2428
- fix(window): honor final duplicate feature values by @mertkayacs in #2436
Events, Input & Timers
- fix(events): reuse resolved identities at EventTarget boundaries #2403
- fix(input): construct mouse and wheel events in the target realm #2395
- fix(input): honor Document execution contexts before default actions #1643
- fix(timers): convert idle request options through WebIDL #2367
- fix(timers): defer idle
- v1.1.21Moli v1.1.212026-11-03 · 2026-11-03 05:54 UTC
查看更新说明
What's Changed
Security, Isolation & Cookie Policies
- fix: validate request headers and randomize CDP identities #2342
- fix(fetch): enforce CORP before redirect and response delivery #2286
- fix(stylesheets): validate integrity before installing CSSOM #2264
- fix(shared-worker): keep agent isolation independent of creator policies #2283
- fix(cookies): apply age-aware Lax-by-default request policy #2181
- fix(cookies): enforce native SameSite contexts for script and network access #2316
Storage, SQLite & IndexedDB
- feat(storage): persist cookies and localStorage in partition SQLite databases #2200
- perf(sqlite): share Tokio task machinery across storage owners #2300
- fix(indexeddb): release finished transaction resources #2210
- fix(indexeddb): keep argument errors in the binding realm #2223
- fix(indexeddb): store native WebTransportError payloads #2112
- fix(cookies): restore Secure cookies with nonsecure source metadata #2236
- fix(storage): scope CDP quota usage to first-party StorageKey #2238
CDP, WebDriver & Browser Automation
- fix(system-info): accept the frontend's browser session (SystemInfo always rejected over real connections) by @neosun100 in #2295
- fix(serve): honor --document-start-script in protocol-server pages by @neosun100 in #2292
- fix(renderer): never reuse backendNodeIds across Documents by @neosun100 in #2293
- fix(webdriver): restore Classic opt-out and BiDi client workflows #2237
- fix(webdriver): preserve scoped node references across Classic and BiDi #2356
- fix(webdriver): unify Classic and BiDi node references #2359
- fix(cdp): resume queued evaluations after history navigation #2358
- fix(cdp): preserve command lifecycle during multi-page Fetch teardown #2163
- fix(cdp): publish timer output while document loading remains pending #2180
- chore(browser-profile): update default Chrome version to 156.0.8078.74 #2227
- perf(drag): read payloads only for accepted interception #2218
Fetch, Referrer & Resource Timing
- feat(dom): retain delivered meta referrer policy on each Document #1358
- fix(referrer): use delivered Document policies for Fetch and XHR #2198
- fix(fetch): propagate explicit referrers and preflight policies #1544
- fix(performance): distinguish cached and validated resource transfers #1678
- fix(fetch): gate Metadata headers on each trustworthy destination #2202
- fix(worker): send typed Fetch Metadata for worker main scripts #2203
- fix(fetch): preserve raw Location bytes across redirect paths #2296
- fix(fetch): preserve combined Content-Type syntax in Body.blob() #2297
Messaging, Workers, Streams & WebSocket
- fix(messaging): derive Window postMessage input conversions #2176
- fix(messaging): prepare transfers before Window target checks #2177
- fix(messaging): preserve closed MessagePort transfer identity #2185
- fix(messaging): cancel closed port queues and release nested transfers #2186
- fix(messaging): preserve accepted Window message source identities #2187
- fix(messaging): preserve opaque origins in Wasm message checks #2193
- refactor(messaging): decouple opaque origins from storage partitions #2234
- fix(clone): allow default payloads with non-default transfer descriptors #2164
- fix(streams): serialize transferred chunks with MessagePort metadata #2173
- fix(streams): cancel readable sources when transfer fails #2174
- fix(streams): notify transfer peers of deserialization errors #2175
- perf(message-port): poll endpoint and queue under one lock #2220
- fix(websocket): ignore unknown binaryType attribute values #2165
- fix(websocket): validate receivers with native brands #2166
- fix(websocket): preserve WebSocketError message code units #2228
- fix(worker): keep navigator caches out of Window identity checks #2219
- fix(runtime): resolve bound owners without author property lookups #2172
- refactor(runtime): remove legacy current-context owner carriers #2204
D
- v1.1.20Moli v1.1.202026-10-30 · 2026-10-30 14:03 UTC
查看更新说明
What's Changed
IndexedDB & SQLite
- feat(indexeddb): replace JSON storage with SQLite #2141
- refactor(indexeddb): use Tokio owners and defer result loading #2153
- feat(indexeddb): add transactional SQLite schema renames #2156
- fix(indexeddb): preserve UTF-16 database name identity #2131
- fix(indexeddb): preserve UTF-16 schema names through SQLite #2157
- fix(indexeddb): expose native readonly schema attributes #2130
- feat(indexeddb): store native DOMRect snapshots with shared codecs #2110
- feat(indexeddb): extend native geometry snapshots with shared codecs #2135
- test(indexeddb): retain live cursor mutation coverage on SQLite #2154
IndexedDB Transactions, Cursors & APIs
- feat(indexeddb): admit transactions by scope and mode #1285
- feat(indexeddb): add native FIFO connection request leases #1283
- feat(indexeddb): coordinate native versionchange acknowledgements #1284
- fix(indexeddb): isolate scoped commits and abort failed transactions #2083
- fix(indexeddb): connect transaction admission across realms and workers #2085
- fix(indexeddb): prepare writes before admitting requests #1956
- fix(indexeddb): preserve native key types and conversion errors #2072
- feat(indexeddb): support native records and bulk query options #2074
- fix(indexeddb): protect cursor state and collect native snapshots #2076
- fix(indexeddb): wait for upgrade requests before completing open #2077
- fix(indexeddb): validate pending cursor iteration #2079
- fix(indexeddb): store native DOMException payloads #2103
- fix(indexeddb): preserve native database connection attributes #2099
- fix(indexeddb): preserve native transaction attributes and abort reasons #2101
- fix(indexeddb): preserve explicit committing transaction state #2105
- fix(indexeddb): abort invalid unique index builds in request order #2109
- fix(indexeddb): deduplicate multiEntry index keys per record #2096
- fix(indexeddb): restore upgrade metadata and handle state on abort #1493
- fix(indexeddb): release rejected synchronous write requests #1287
- refactor(indexeddb): derive transaction scope and mode conversion #1998
- fix(indexeddb): expose required argument counts in method lengths #1806
- fix(indexeddb): expose a readonly worker prototype accessor #656
- fix(indexeddb): reserve open results before upgrade completion callbacks #2114
- fix(indexeddb): preserve native request attributes and factory result state #2115
Fonts & Font Loading
- fix(fonts): reject malformed FontFace payloads #312
- fix(fonts): share native FontFace loading across CSS and JS #492
- fix(css): enable native variable font properties #1778
- fix(cssom): serialize computed font from native longhands #1780
- fix(css): use native font size adjustment shorthand semantics #1811
- fix(fonts): bind FontFace requests to their creation Window #1886
- fix(fonts): retain aggregate query Promise settlement state #1888
- fix(fonts): normalize computed descriptor range endpoints #1889
- fix(fonts): share full capability ranges with native layout #1890
- fix(fonts): include resolved fallback sources in resource identity #1891
- perf(fonts): reuse payloads and incrementally register faces #367
- fix(fonts): retain pending loads across promise chains #1899
- fix(fonts): retain registrations shared by CSS declarations #1900
- fix(fonts): keep CSS-connected faces out of manual membership #1901
- fix(fonts): admit local-only CSS font declarations #1902
- fix(fonts): preserve generic families in font-set queries #1903
- fix(fonts): share Chromium-compatible Unicode family matching #1904
- perf(fonts): register validated font data without copying #1908
- fix(fonts): separate declaration identity from shared sources #1909
- refactor(fonts): reuse native descriptors for font queries #1910
- perf(fonts): batch membership retirement and update priorities in place #1911
- refactor(fonts): name font selection priorities #1913
- fix(fonts): decouple
- v1.1.19Moli v1.1.192026-10-21 · 2026-10-21 20:55 UTC
查看更新说明
What's Changed
CDP, Inspector & Browser Lifecycle
- feat(cdp): support instance-scoped Browser.close shutdown by @254808127 in #1771
- test(cdp): verify Browser.close process exit and profile reopen by @BibekPathak in #815
- refactor(cdp): use shared CDP dispatch for browser instance control #1772
- fix(cdp): unblock init scripts and gate every smoke group on Chromium #1718
- fix(cdp-smoke): retain load events during debugger-wait setup #1754
- fix(inspector): make debugger pauses opt-in at session creation #1816
- fix(cdp-smoke): enable debugger pauses for Moli scenarios #1828
Navigation, Frames & Window
- fix(renderer): synchronize child Documents in their current Realms #1638
- fix(renderer): project Node and Window events into listener worlds #1563
- fix(window): enforce parent-origin and native receiver access checks #1651
- fix(navigation): preserve foreign child navigation semantics #1658
- fix(navigation): guard fragment routing and stale facade targets #1701
- fix(forms): enforce sandbox checks for ancestor navigation #1773
- fix(page): load a new document when navigation removes a fragment #1824
- fix(navigation): keep hash setters within the current document #1786
- fix(script): keep parser async work runnable after EOF #1634
Fetch, XHR & Networking
- fix(fetch): preserve worker abort reasons in response bodies #1620
- fix(fetch): release body resolver realm roots on completion #1615
- fix(fetch): revalidate disk cache for request control headers #1667
- feat(fetch): preserve native response timing observations #1666
- feat(fetch): observe native request and interim response timing #1679
- fix(fetch): align HTML transfer timing and reduce observer overhead #1702
- refactor(fetch): clarify transport redirects and cancellation #1653
- fix(fetch): preserve binary uploads across Window and Worker interception #1646
- perf(fetch): share immutable request bodies with Bytes #1706
- fix(xhr): encode URL queries with the owning document charset #1799
- fix(fetch): parse bare CR response fields like Chromium #1825
- fix(fetch): match Chromium for bare-CR header EOF #1832
File API, Streams & Encoding
- fix(file-api): remove the initial UTF-8 BOM from Blob.text #1625
- fix(file-api): avoid overflow when normalizing Blob slice offsets #1619
- fix(file-api): convert constructor inputs before processing Blob parts #1626
- fix(file-api): stop Blob.slice at the first conversion error #1709
- feat(streams): implement native ReadableStream.from iteration #1035
- fix(encoding): snapshot decoder input after options conversion #1665
- fix(encoding): use standard label parsing for text decoders #1662
- fix(encoding): validate decoder stream chunks with Web IDL #1674
- fix(encoding): initialize decoder streams through native endpoints #1668
- fix(encoding): preserve UTF-16 boundaries in encoder streams #1675
- fix(dom): retain encodings in native Document state #1798
DOM, Messaging & WebIDL
- fix(dom): retarget adopted shadow trees before reinsertion #1624
- fix(dom): initialize media state from defaults when cloning #1623
- fix(navigator): freeze and cache languages per realm #1648
- fix(url): create query objects with intrinsic bindings #1660
- refactor(webidl): derive dictionary snapshots for RTC configuration #1705
- refactor(webidl): migrate dictionary snapshots and backing state #1707
- feat(webidl): add clamped long long conversion and derive support #1708
- fix(messaging): validate MessagePort state in author transfer order #1739
- fix(dom): validate Range DocumentType contents before mutation #1736
CSS, Layout, Fonts & Printing
- feat(layout): upgrade Taffy and Stylo with sizing and overflow fixes #1745
- perf(layout): allocate numeric scratch only for used boxes #1604
- perf(layout): expose stage timings and a native cost probe #1605
- fix(cssom): refresh child media after iframe resize #1631
- fix(layout): project Resize
用户点评
登录用户的真实使用记录,可按版本和硬件筛选还没有人点评。第一个说出真实体验的人最有价值。
Moli is a production-ready headless browser for AI agents. Its on-demand layout and rendering design combines a complete browser runtime with a lightweight resource footprint.
Moli helps your AI agent fetch and extract web pages, search the web, and automate browser tasks.
Use it through the CLI, CDP, WebDriver Classic, or WebDriver BiDi.
Moli supports Linux, macOS, and Windows.
Quick start
Give this prompt to your AI agent:
Install the skills under https://github.com/lexmount/moli/tree/main/skills,
follow their instructions to download and install the latest prebuilt Moli
binary, then use moli-webfetch to fetch https://example.com and show me the
result.
Direct installation
On Linux or macOS:
curl --proto '=https' --tlsv1.2 -fsSL \
https://github.com/lexmount/moli/releases/latest/download/moli-installer.sh | sh
On Windows, run in PowerShell:
powershell -ExecutionPolicy ByPass -c "irm https://github.com/lexmount/moli/releases/latest/download/moli-installer.ps1 | iex"
Showcase
CLI usage
Extract a page
Render the page as Markdown with Moli's default completion strategy:
moli fetch \
--dump markdown \
--wait-until done \
https://example.com
Or directly return a compact, model-friendly semantic tree:
moli fetch \
--dump semantic_tree_text \
--wait-selector body \
https://example.com
For visual output, enable on-demand layout and write a viewport PNG, a full-document PNG, or a paginated PDF:
moli fetch --layout --dump screenshot https://example.com > page.png
moli fetch --layout --dump screenshot_full https://example.com > full-page.png
moli fetch --layout --dump pdf https://example.com > page.pdf
To export a page after an explicit interaction, combine --eval (or
--eval-file) with --dump. The expression runs after page readiness;
promises are awaited, and any pending navigation is followed before export.
Its return value is discarded without serialization. An exception fails the
command without emitting a dump.
moli fetch --eval 'document.querySelector("button.show-details").click()' \
--dump markdown https://example.com
Only the supplied expression performs interactions. Without --dump,
--eval retains its expression-value output behavior.
Run fetch --help for the complete option list, including output formats,
page-load/response waits, profiles, proxy settings, resource policies, and
tracing options.
Start the automation server
# Basic automation server for DOM-first workloads
moli serve
# Enable real geometry, coordinate input, and screenshot/screencast surfaces
moli serve --layout
# Also fetch optional image, font, audio, video, media, and text-track resources
moli serve --layout --resource
The same endpoint serves all three protocols: CDP, WebDriver Classic, and WebDriver BiDi. Playwright can connect directly over CDP:
import { chromium } from "playwright";
const browser = await chromium.connectOverCDP("http://127.0.0.1:9222");
const context = browser.contexts()[0];
const page = context.pages()[0] ?? await context.newPage();
await page.goto("https://example.com");
console.log(await page.locator("body").innerText());
await browser.close();
The WebMCP playground includes a CLI to list and call any site's native WebMCP tools, plus booking, restaurant, and pizza demos.
Why Moli
Three qualities matter most for agent workloads, and Moli brings them together:
- Full-featured — real JavaScript, DOM, CSS, networking, storage, layout, screenshots, and standard automation protocols, all integrated into one headless browser.
- Fast — most automation requests never need visual rendering, so structure-first operations skip layout and paint entirely.
- Resource-efficient — layout and pixels are generated only when needed, so Moli does not have to continuously maintain and update a fully rendered visual state.
What most browser automation tasks actually need is page structure, not a continuously rendered visual world. Moli treats the native DOM and style state as the single source of truth, triggering layout or software paint only for operations that genuinely require them.
| Agent request | What Moli does |
|---|---|
| Extract HTML/Markdown, query the DOM, run JS, inspect network/storage | Reads browser runtime state directly — does not trigger layout or paint |
| Read an element's box, hit-test coordinates, send coordinate input | Runs one layout calculation and retains only the latest frozen layout tree |
| Capture a screenshot | Rebuilds from the current DOM/style, replaces the frozen tree, renders a fresh frame, and discards its paint state after use |
| Poll a screencast | Compares generation metadata only; clean state emits no frame, while changed state rebuilds and emits one fresh frame |
Moli still includes the complete set of capabilities: V8, CSS, layout, text shaping, hit-testing, software paint, and more. The only difference is when visual work runs and how long its results are retained. This cost model is especially well suited to crawling, browser-use agents, retrieval pipelines, evaluation environments, and reinforcement-learning workloads.
Current capabilities
- Complete web runtime — streaming HTML parsing, native DOM, V8 JavaScript, modules/timers/microtasks/events, iframes and workers, CSS cascade, Fetch/XHR/WebSocket, cookies, WebCrypto, and profile-scoped storage (localStorage, IndexedDB, OPFS).
- Extraction-optimized outputs — the CLI directly produces HTML, Markdown, JSON, semantic text trees, and frame-aware serialization, with selector/script/response waits and network tracing.
- Unified automation binary — CDP, WebDriver Classic, and WebDriver BiDi share the same kernel and scheduler. No separate ChromeDriver, geckodriver, or browser installation is required.
- Real visual capabilities on demand — add
--layoutto enable complete box construction, Taffy layout, Parley text layout, layout-backed hit-testing/input, viewport screenshots, and low-frequency CPU-rendered DevTools screencast frames. - Controllable operational options — profiles, cookies, HTTP cache, proxies, resource families, connection limits, timeouts, private-network policy, user-agent overrides, diagnostic logging, and network diagnostics are all available.
Moli's relationship with Lexmount
Moli is Lexmount's open-source headless browser; Lexmount Browser is the managed cloud runtime and control plane built around it.
The open-source headless browser is fully usable without Lexmount Browser.
Cost controls
Expensive browser operations in Moli require an explicit opt-in and are never enabled by default:
| Mode or option | Behavior |
|---|---|
| Default | LayoutPolicy::Mock — deterministic geometry in a compatible format, with no real layout or paint |
--layout | LayoutPolicy::OnDemand — real layout, geometry, hit-testing, coordinate input, screenshots, screencast |
--resource | Fetch all optional visual/media resource families |
--image, --font, --audio, --video, --media, --text-track | Enable one specific optional resource family |
--profile-dir, --http-cache-dir, --cookie-file | Selectively enable the persistence required by the workload |
Layout is an on-demand snapshot rather than continuously maintained state. The
first geometry request (a cold start) builds a working layout tree from the
current DOM/style, freezes its canonical geometry into an immutable,
DOM-independent FrozenLayoutTree, and retains only that latest tree. Ordinary
geometry reads may reuse it even if the page has changed. Screenshots always
rebuild and replace the frozen tree. Each screencast subscription remembers
only an opaque visual-state token: an unchanged token suppresses the poll,
while a changed token triggers one fresh frame. Paint results are never reused.
Architecture
Moli is a standalone browser kernel, not a Chromium wrapper. It is built in Rust, has its own ownership and lifecycle rules, and relies on:
libcurl— network transport and multi-request runtimehtml5ever— HTML parsingrusty_v8/ V8 — JavaScript execution- Servo/Stylo — selectors, cascade, computed style
- Taffy + Parley — box and text layout
- AnyRender/Vello CPU,
usvg, and the Rust image ecosystem — software rendering
Document and style have a single source of truth: the native DOM and its Stylo
integration. Every real refresh builds a temporary working tree from that
source, optionally produces and consumes one fresh paint snapshot, freezes the
canonical box/fragment geometry into a compact FrozenLayoutTree, and discards
the working tree, style borrows, layout caches, diagnostics, and paint state.
Screencast tokens contain generation metadata, never layout or paint data.
Source lookup and hit-test candidates are derived from the frozen tree when
queried. The system has no incrementally maintained layout tree, damage graph,
retained display list, GPU compositor, or persistent window.
Benchmark
The following measurements show Moli's current capability envelope. They cover real websites, automation clients, Chromium/WPT behavior checks, and a large nextest regression suite.
Mixed public-web crawl test
The test covers 192 public URLs from major Chinese and international sites. A page only counts as successful if it produces meaningful content after JavaScript runs — an HTTP 200, challenge page, login wall, empty response, or shell-only application does not count.
| Browser | Useful pages | Success rate | Median time | Median RSS |
|---|---|---|---|---|
| Moli | 103 | 53.6% | 1.43 s | 73 MiB |
| Chrome Headless | 101 | 52.6% | 1.43 s | 773 MiB |
| Lightpanda | 85 | 44.3% | 0.97 s | 40 MiB |
| Obscura | 57 | 29.7% | 1.30 s | 39 MiB |
Sample agent workload
| Metric | Moli | Chromium |
|---|---|---|
| CDP ready | 34.85 ms | 169.37 ms |
| Episode active p50 | 33.40 ms | 57.13 ms |
| Peak PSS | 102.46 MiB | 348.82 MiB |
| Peak processes / threads | 1 / 24 | 11 / 123 |
WPT tests
In the current WPT selection used to validate Moli's agent-browser scope, one full run passed 1.612 million tests.
Moli's performance in Lexbench-Headless-Browser
The full Lexbench-Headless-Browser corpus contains 1,928 tasks covering raw CDP, 13 pinned automation tools including Playwright, Puppeteer and Selenium, and web-platform semantics. To include Kitesurf, which is only available as a remote endpoint, the chart below uses 1,308 comparable tasks. All browsers use the same task selection rules.
Moli 0.1.1 passed 1,071 tasks, a success rate of 81.88%, ahead of Kitesurf at 62.08%, Lightpanda at 53.29%, and Obscura at 44.88%; Chrome, the reference engine, reached 99.85%. Kitesurf ran at k=1, uncovered tasks count as not passed, and the reproduction conditions of a remote service differ from those of local binaries. See the benchmark's five-engine report for the full results.
Kitesurf is a remote service, so its CPU, memory, and process counts cannot be measured. The resource comparison therefore covers only the four local engines. A separate 557-task run includes only work completed by all four. Moli's median CPU time per task was 100.6 ms and its median peak memory was 92 MiB; Chrome recorded 687 ms and 697 MiB, respectively. Moli used about 15% of Chrome's CPU time and 13% of its peak memory. See the benchmark's resource card for the methodology and full data.
Project scope
Within the agent-browser scenarios defined in its documentation, Moli is ready for production use and remains under active development.
Its current intentional boundaries include:
- No GUI browser, persistent window, GPU compositor, or retained multi-frame paint architecture.
- It does not pursue pixel-for-pixel parity with Chrome or provide high-fidelity Canvas/WebGL/media playback.
--layoutsupports software screenshots and native CDP/CLI PDF with embedded fonts generation, but not every Chrome screenshot or print mode is implemented.
Unsupported protocol paths return explicit errors — Moli never pretends that a browser action, event, network observation, or visual result occurred.
Star history
Generated hourly from the stargazer timeline by lexmount/moli-metrics.
License
Unless a file or directory states otherwise, you may use Moli under either the
Apache License 2.0 or the MIT License, at your
option. Separately licensed third-party components and fixtures remain subject
to their own licenses and notices. license-metadata.json
records the machine-readable default, and additional distributable notices are
collected under licenses/.