跳到正文
Hacker News· Riiiiichard·· 5 小时前AI 评分71

zebkit 0.4.0 发布:把 Chromium 引擎做成可嵌入库

Zebkit – Chromium as an embeddable library

AI 导读

zebkit 0.4.0 发布,把 Chromium 157.0.8103.38 的 Blink、V8、PDFium、FFmpeg 等打包成一个可链接进自有应用的库,不含 Chrome 浏览器和 Electron 运行时,提供 C、Rust、Python、Go、JavaScript 绑定。

正文 · AI 翻译

译文尚不完整,完整内容请切换到原文。

zebkit

Chrome 作为库——轻量、快速、随处可用、简单易用。
在 Linux、macOS 和 Windows 上统一一套 API。几行代码即可接入。

crates.io PyPI Engine release

简介

zebkit 将 Chromium 的引擎作为一个库,供你链接进自己的应用程序:Blink、包含全部 JIT 层级的 V8、PDFium、FFmpeg 以及 Chrome 的其余组成部分,但不含 Chrome 浏览器的任何部分,也不含 Electron 的运行时。你的应用程序仍然属于你,可用 C、C++、Rust、Python、Go 或 JavaScript 编写,拥有自己的窗口和工具包。需要 Web 时,它添加一个 zebkit 视图;需要 PDF、媒体、压缩或加密时,它调用 zebkit 的 API:用 C,或通过 Rust、Python、Go 和 JavaScript 的绑定(zebkit-js,以及在应用程序自己的页面中的 zebkit.*)。

The application links one library, zebkit, which runs Chromium's content layer, Blink and V8 inside the application's process; pages run in sandboxed helper processes; on Linux, macOS and Windows

  • 集成它,而不是基于它构建。Electron 是一个基础:应用程序在它内部构建,使用它的运行时、窗口和打包方式。zebkit 是一个部件:应用程序添加它,就像添加 SQLite 一样,并保留自己的进程、工具包和发布方式。它运行在应用程序自己的事件循环中(zebkit_init()、zebkit_pump()),并绘制在应用程序的窗口中,可以是你绘制的帧,也可以是你提供给它的窗口的子窗口、一个 X11 窗口、一个 NSView 或一个 HWND(examples/native-host),并返回页面所在的窗口供系统对话框使用。
  • 按需取用。Web 视图,可在窗口中或离屏使用,帧交给你处理;完整的 PDF API;保存页面播放的内容;自定义 URL 方案;从 JavaScript 使用原生 UI;面向智能体的 Playwright API 和 CDP。用一个,或全部。
  • 真正的 Chrome 引擎。页面看到的是 Chromium 157:其用户代理和客户端提示、window.chrome、未经点击即被拦截的弹窗,且没有附加任何调试器(docs/API.md 列出了仍有差异的地方,其中包括 Google 登录)。
  • 比 Electron 更轻。与最新的 Electron 45.2.1 相比:体积小 34%,冷启动 2.3 秒对 6.6 秒,页面内存少 29%,以及 DOM、文本布局和 JavaScript 水平(基准测试)。
  • 没有 Node。没有第二个 JavaScript 运行时,也没有通过它进行的 IPC。应用程序是原生代码,或是由 zebkit-js 在引擎的 V8 上运行的 JavaScript。
  • Chromium 的库归你所用。引擎内有 17 个 C 库,在 Linux、macOS 和 Windows 上各有约 7,490 个函数(三者共有 7,487 个,其余为各系统自有),在 zebkit_vendor_* 下导出:SQLite、LevelDB、BoringSSL、zlib、Zstandard、Brotli、ICU、HarfBuzz、libxml2、libwebp、libjpeg-turbo、FFmpeg、dav1d、Opus、libvpx、FreeType 和 PDFium。你无需重复打包它们。
  • 内置 PDF。基于 Chromium 的 PDFium 的完整 API:渲染与瓦片、带布局的文本、搜索、大纲、链接、如同编辑器所具备的注释(线端、云状边框、带标注和富文本的自由文本、图章、回复与状态、测量、XFDF 和 FDF)、表单、涂黑、页面编辑、合并、附件、元数据,以及校验完整性的签名;为应用程序自有注释类型、外观、带撤销的事件、渲染层、OCR、字体、信任和操作提供钩子。支持 C、Rust、Python 和 JavaScript。
  • 2D 绘制与字形处理。基于引擎 Skia 的 <zebkit/canvas.h> 和基于其 HarfBuzz 的 <zebkit/text.h>,无需再次打包二者。
  • 媒体,边播放边保存。页面播放的任何内容——文件、MSE/DASH 或 HLS——都按其发送时的原样写入 MP4 或 WebM,无需重新编码,也无需二次下载(<zebkit/media.h>)。
  • 字节保持原样。页面对应用程序的调用通过其 frame 自己的 Mojo 通道进行,大调用则通过共享内存:无需 base64, 页面的 JSON 按写入时的原样传递,小调用与 Electron 的 IPC 一样快, 大调用则更快。
  • 页面周围的窗口,由 JavaScript 驱动。zebkit.ui 使用 Chromium 的 views 工具包 绘制窗口、提示框和对话框:信号、 JSX、Tailwind 类,以及用于测试和代理的自动化。丰富的 UI 是一个 页面;zebkit.ui 是它周围的 chrome(docs/UI.md)。
  • Playwright 的 API,原生实现。整个 Playwright(定位器、 等到可以执行时才执行的操作、键盘、鼠标和触摸、 路由、对话框、下载、上下文、worker、trace、HAR、视频)都在 引擎内部运行,无需 CDP 或 Node.js 驱动:比通过 CDP 的 Playwright 快 3 到 10 倍, 一次点击只需 3 到 5 毫秒,而不是 33 毫秒 (<zebkit/automation.h>、docs/AUTOMATION.md)。Playwright 自带的 客户端,无论哪种语言,都能通过 chromium.connect() 原样连接到它。
  • 为代理而打造。tools/zebkit-mcp 将页面、PDF、文件、媒体 和原生窗口作为 MCP 工具提供给它们,通过标准输入和 HTTP,在 其操作者设定的范围内(docs/MCP.md)。它们也可以通过 Playwright 的 API 或通过 CDP(Chromium 自带的 DevTools 服务器)驱动 zebkit,无头或 有窗口均可。
  • 从你的代码中定制它,直至桌面。原生钩子决定 引擎的行为,支持 C、Rust、Python 和 JavaScript:权限、 导航、弹窗、对话框、下载、证书和客户端 证书、登录、每个阶段的网络请求和响应、 运行时设置、你自己的 URL 方案、页面可以调用什么、页面自身脚本之前 运行的脚本(docs/ROADMAP_HOOKS.md)。页面或 引擎会向人展示或向系统请求的一切都经过 应用程序:窗口的框架(系统、覆盖或无,以页面作为 标题栏)、菜单和上下文菜单、JavaScript 对话框和登录、 文件和设备选择器、屏幕捕获、通知、托盘、 Dock 和任务栏、共享、系统主题、电源和显示器 (融入桌面、 docs/SYSTEM_INTEGRATION.md)。
  • 默认安全。除非应用程序 按视图、按来源和按能力授予,页面没有 zebkit 对象; 来源是浏览器的,绝不是页面的说法;渲染器保持 沙箱化;浏览器会询问的权限不会被授予。
引擎 Chromium 157.0.8103.38(upstream.lock):内容层、Blink、带所有 JIT 层级的 V8
Linux x86-64,glibc 2.17 或更新版本(CentOS 7 及更高版本),X11 和(--ozone-platform=wayland)Wayland:libzebkit.so.1 和 zebkit-helper
macOS Apple 芯片:Zebkit.framework,原生构建或在 Linux 上交叉构建(docs/MACOS.md)
Windows x64,Windows 10 或更新版本:zebkit.dll 和 zebkit-helper.exe,在 Linux 上交叉构建(docs/WINDOWS.md)
版本 VERSION:0.4.0

快速开始

获取引擎

一个包是一个目录,包含库、辅助程序、引擎的数据 和 include/。可以解压某个发行版的归档文件 (来自 GitHub releases 的 zebkit-<version>-<target>.tar.xz),或者构建一个:

mise run r      # dist/release (Linux), dist/release-macos (macOS), dist/release-windows; hours the first time

每个归档文件还包含工具,为其系统构建,与库放在一起, 解压后即可运行:zebkit-run、zebkit-js 和 zebkit-mcp(Windows 上为 .exe,在那里同样沙箱化)。

在安装了完整 Xcode 的 Apple 芯片 Mac 上,scripts/dist.sh release 将引擎本地构建到 dist/release。然后运行 scripts/example.sh macos-basic 来构建并启动 dist/example/Zebkit Embedded Browser.app 中的 C 嵌入演示。

构建 Chromium 的代码树需要约 30 GB(docs/BUILD.md)。下文中的 $PKG 是包目录。

将其添加到已有的程序中

zebkit 是一个库:你的程序保留自己的 main() 并调用它。这个示例将 PDF 的第一页渲染为 PNG 并读取其文本,PDFium 和图像编码器都在 zebkit 内部,无需安装其他东西:

#include <stdio.h>
#include <zebkit/pdf.h>
#include <zebkit/services.h>

/* Your program, your main(): zebkit is just a library it calls. */
int main(int argc, char **argv) {
    char *error = NULL;
    zebkit_pdf *pdf = zebkit_pdf_open_file(argv[1], NULL, &error);
    if (!pdf) {
        fprintf(stderr, "%s\n", error);
        return 1;
    }

    /* The first page as a PNG, at twice its size. */
    zebkit_pdf_render_options options = {.scale = 2, .background = 0xffffffff};
    int width, height;
    uint8_t *rgba = zebkit_pdf_render(pdf, 0, &options, &width, &height, &error);
    size_t size;
    uint8_t *png = zebkit_image_encode(ZEBKIT_IMAGE_PNG, rgba, width, height, 0, &size, &error);
    fwrite(png, 1, size, fopen("page1.png", "wb"));

    /* And its text. */
    char *text = zebkit_pdf_text(pdf, 0, &error);
    printf("%d pages; page 1 says: %.80s\n", zebkit_pdf_page_count(pdf), text);

    zebkit_free(text), zebkit_free(png), zebkit_free(rgba);
    zebkit_pdf_close(pdf);
    return 0;
}
cc thumb.c -I$PKG/include -L$PKG -l:libzebkit.so.1 -Wl,-rpath,$PKG -o thumb   # Linux
cc thumb.c -I$PKG/include -F$PKG -framework Zebkit -Wl,-rpath,$PKG -o thumb   # macOS
./thumb report.pdf     # 2 pages; page 1 says: Quarterly report ...

PDF API、压缩、加密、URL、图像编解码器、ICU 以及引擎内部的库(zebkit_vendor_*,其中包括 FFmpeg 的库)不需要引擎循环(docs/API.md);只有通过引擎网络栈的 HTTP 和网页才需要。

添加 Web 视图

网页需要引擎运行。这里引擎运行循环:程序用 zebkit_main() 启动它,并在其回调中工作。拥有自己循环的程序(GTK、Qt、AppKit、winit)用 zebkit_init() 启动它,并调用 zebkit_pump()(examples/native-host)。

#include <stdio.h>
#include <zebkit/zebkit.h>

static void printed(zebkit_view *view, const char *json, const char *error, void *data) {
    printf("%s\n", json ? json : error);
    zebkit_quit();
}

static void loaded(zebkit_view *view, zebkit_load_event event, const char *url, void *data) {
    if (event == ZEBKIT_LOAD_FINISHED)
        zebkit_view_evaluate(view, "document.title", printed, NULL);
}

static void ready(void *data) {
    zebkit_view *view = zebkit_view_new("https://example.com/", 1024, 768);
    zebkit_view_on_load(view, loaded, NULL);
}

int main(int argc, char **argv) {
    return zebkit_main(argc, argv, ready, NULL);
}
cc app.c -I$PKG/include -L$PKG -l:libzebkit.so.1 -Wl,-rpath,$PKG -o app   # Linux
cc app.c -I$PKG/include -F$PKG -framework Zebkit -Wl,-rpath,$PKG -o app   # macOS
clang-cl /MT app.c /I%PKG%\include %PKG%\zebkit.dll.lib %PKG%\zebkit_sandbox.lib   # Windows
./app --remote-debugging-port=9222     # with CDP for Puppeteer and Playwright

在 Windows 上,可执行文件参与 Chromium 的沙箱:它链接 zebkit_sandbox.lib,并在 main() 中首先调用 zebkit_win_startup()(docs/WINDOWS.md)。

没有非特权用户命名空间时,引擎拒绝启动并说明原因:安装其辅助程序,或在其自己的容器中添加 --no-sandbox(docs/SANDBOX.md)。

页面与应用程序

应用程序通过一个 scheme 提供自己的页面,并赋予它们可调用的函数;除非被授予,页面没有 zebkit 的任何内容。

/* The page calls zebkit.app.greet("Ada"). */
static void greet(zebkit_view *view, const char *origin, const char *args_json,
                  zebkit_call *call, void *data) {
    char answer[256];
    snprintf(answer, sizeof answer, "{\"hello\": %s}", args_json);
    zebkit_call_resolve(call, answer);
}

/* app://main/... from ./ui, in ranges for media. A real handler refuses "..". */
static void serve(zebkit_request *request, void *data) {
    char path[1024];
    snprintf(path, sizeof path, "ui%s", zebkit_request_url(request) + sizeof "app://main" - 1);
    zebkit_request_respond_file(request, path, NULL);
}

static void closed(zebkit_view *view, void *data) { zebkit_quit(); }

static void ready(void *data) {
    zebkit_view *view = zebkit_view_new("app://main/index.html", 1024, 768);
    zebkit_view_grant(view, "app://main", ZEBKIT_ALLOW_APP | ZEBKIT_ALLOW_PDF, NULL);
    zebkit_view_bind(view, "greet", greet, NULL);
    zebkit_view_on_close(view, closed, NULL);
}

int main(int argc, char **argv) {
    zebkit_scheme_register("app", ZEBKIT_SCHEME_STANDARD | ZEBKIT_SCHEME_SECURE | ZEBKIT_SCHEME_FETCH);
    zebkit_scheme_handle("app", serve, NULL);
    return zebkit_main(argc, argv, ready, NULL);
}

在页面中,每次调用都是一个 promise,字节是 Uint8Array:

const reply = await zebkit.app.greet('Ada');            // the application's function
const pdf = await zebkit.pdf.open(await (await fetch('report.pdf')).bytes());
const rgba = await pdf.render(0, {scale: 2});           // a page as pixels
const hits = await pdf.search('revenue');
const packed = await zebkit.compress('zstd', bytes, 19);
const hash = await zebkit.crypto.digest('SHA-256', 'abc');

融入桌面

引擎要向人展示的每样东西,或向系统请求的每样东西,都有一个可用的默认值,以及一个可替换它的应用程序函数。没有应用程序可以做出的决定,任何东西都不会到达系统;没有函数时,页面得到谨慎浏览器所给予的东西,而在浏览器会询问的地方则什么也得不到。

import zebkit

zebkit.scheme_register("app")   # served by a scheme_handle(), as in the C example above
zebkit.init(["app"])
# The page is the title bar: the window's buttons over it, dragged by its CSS.
view = zebkit.View("app://main/index.html", 1000, 700, options={
    "frame": "overlay", "controls": {"height": 40, "color": "#1b1d2a", "symbolColor": "#ffffff"},
    "dragRegions": {"source": "css", "origins": ["app://main"]}})

# The application's menu bar (macOS) and keys, with state that changes in place.
zebkit.app_set_menu([{"title": "File", "items": [{"title": "Autosave", "id": "autosave", "checked": True}]}],
                    lambda item: zebkit.app_update_menu_item(item, checked=False))

# A tray icon, the Dock's or the taskbar's badge, and the page's notifications as the application's.
tray = zebkit.Tray(open("icon.png", "rb").read(), "My application")
zebkit.app_set_badge("3")
zebkit.on_notification(lambda event: print("show in my own UI:", event))

# What a page asks for: a USB device, a screen to capture, a colour, a share.
view.on_device_chooser(lambda request: request.respond(request.metadata["devices"][0]["id"]))
view.on_media_access(lambda request: request.respond(True, None, "view"))
view.on_share(lambda request: request.respond(True))

# The system: its theme and its changes, the session's lock, the screens.
zebkit.on_environment_changed(lambda env: print("dark" if env["colorScheme"] == "dark" else "light"))
zebkit.on_power_event(lambda event: print(event["event"]))   # suspend, resume, lock, unlock
print(zebkit.displays())
默认 应用程序的
窗口框架 系统的 overlay(Window Controls Overlay)或 none、标题、颜色、拖动区域、状态、操作
菜单 macOS 上的菜单栏;默认上下文菜单 按 id 带状态的项;上下文菜单被替换、扩展或运行
对话框 系统的警报;有窗口时引擎的提示和登录 zebkit_view_on_dialog、on_http_auth、on_client_certificate、取色器
文件与设备 系统的文件对话框;无设备 文件选择器和 File System Access;WebUSB、WebHID 和 Web Serial 选择器
捕获与媒体 经许可的摄像头和麦克风;无屏幕 getDisplayMedia() 的来源:视图本身或屏幕
通知 未决定;然后是系统自己的服务 在应用程序的 UI 中显示,点击和关闭会回传给页面
桌面 无 托盘、Dock 和任务栏的徽章、进度和注意力、Dock 菜单、Web Share、打开 URL 和文件夹
系统 浅色页面 实时跟随系统的深色模式和对比度;电源、锁定和显示事件;保持唤醒

docs/SYSTEM_INTEGRATION.md 有每个界面、其默认值、在每个系统上的状态及原因;docs/API.md 有四种语言中的调用;examples/custom-titlebar 是一个标题栏即页面的窗口。

Rust

crates.io 上的 zebkit,用 zebkit-build 将引擎放在你的程序旁边;构建时会为你的系统下载一次引擎并校验其 SHA-256。

[dependencies]
zebkit = "0.4.0"

[build-dependencies]
zebkit-build = "0.4.0"
// build.rs: puts the engine beside the executable
fn main() { zebkit_build::bundle(); }

// main.rs
fn main() {
    std::process::exit(zebkit::run(|| {
        let view = zebkit::View::new(Some("https://example.com/"), 1024, 768);
        view.grant("https://example.com", zebkit::Allow::APP, None);
        view.bind("greet", |call| {
            let answer = format!("\"hello, {}\"", call.args_json());
            call.resolve(&answer)
        });
        view.on_close(zebkit::quit);
    }));
}

ZEBKIT_DIR 将构建指向一个包(bindings/rust/README.md)。

Python

PyPI 上的 zebkit 是绑定;引擎是同一版本 releases 中适用于你系统的归档,用 ZEBKIT_DIR 指向它:

pip install zebkit
export ZEBKIT_DIR=/path/to/zebkit-0.4.0-x86_64-unknown-linux-gnu
import zebkit                      # pure Python on ctypes: nothing to compile

def ready():
    view = zebkit.View("https://example.com/")
    view.grant("https://example.com", zebkit.ALLOW_APP)
    view.bind("greet", lambda name, origin: f"hello, {name}")

zebkit.main(ready)                 # the engine is found in $ZEBKIT_DIR

Go

bindings/golang,基于 cgo:视图、页面调用 Go 函数、应用程序的 scheme、页面钩子和系统服务,在 Linux 和 macOS 上。使用引擎的头文件和库构建:

go get github.com/ikey4u/zebkit/bindings/golang@main
export CGO_CFLAGS="-I$ZEBKIT_DIR/include" CGO_LDFLAGS="-L$ZEBKIT_DIR -Wl,-rpath,$ZEBKIT_DIR"
os.Exit(zebkit.Main(func() {
    view := zebkit.NewView("", 1024, 768)
    view.Grant("https://example.com", zebkit.AllowApp, "")
    view.Bind("greet", func(call *zebkit.Call) {
        call.Resolve(fmt.Sprintf("\"hello, %s\"", call.Args))
    })
    view.OnClose(zebkit.Quit)
    view.Load("https://example.com/")
}))

JavaScript,无需 Node

zebkit-js 在引擎的 V8 上运行 ES 模块,适用于用 JavaScript 编写的应用程序,具有视图、相同的 API 和原生窗口:

// zebkit-js main.mjs
const view = new zebkit.View('app://main/index.html', {width: 1024, height: 768});
view.grant('app://main', ['app', 'pdf']);
view.bind('save', async (name, text) => {
  await zebkit.fs.writeFile(`${zebkit.args[0]}/${name}`, text);
  return {saved: true};
});
view.on('close', () => zebkit.quit());
// zebkit-js examples/ui/todo.mjs: a window of native widgets, styled with
// Tailwind's classes and kept up to date by signals (an excerpt)
const {h, signal, computed, For, window} = zebkit.ui;
const [tasks, setTasks] = signal([]);
const done = computed(() => tasks().filter((t) => t.done).length);

const Task = (task) =>
  h('view', {class: 'flex-row items-center gap-3 p-3 rounded-xl bg-[#141a2e]'},
    h('button', {class: 'w-8 h-8 p-0 rounded-full bg-[#7c5cff] text-white',
                 onClick: () => toggle(task)}, task.done ? '✓' : ''),
    h('text', {class: 'grow text-[#e6e8f2]'}, task.text));

window({title: 'Today', width: 460, height: 680}, () =>
  h('view', {class: 'flex-col gap-4 p-6 bg-[#0b1020]'},
    h('text', {class: 'text-3xl font-bold text-white'}, 'Ship something great'),
    h('text', {class: 'text-sm text-[#8a91aa]'}, () => `${done()} of ${tasks().length} done`),
    h(For, {each: tasks}, Task)));

examples/ui/todo.mjs

代理

Playwright 的 API 运行在引擎内部(docs/AUTOMATION.md):Playwright 协议的每条命令都在应用进程内执行,不经过 CDP,也不依赖 Node.js。从 C 语言调用只需一次调用,zebkit_automation_call(view, "Frame.click", "{\"selector\": \"#go\"}", done, data);Python 中则以 Playwright 的名称提供:

import zebkit
from zebkit.automation import Page, expect, run

async def test():
    page = Page()
    await page.goto("https://example.com/")
    await expect(page).to_have_title("Example Domain")
    await expect(page.get_by_text("documentation examples")).to_be_visible()
    await page.screenshot(path="example.png")
    zebkit.quit()

zebkit.main(lambda: run(test()), ["python", "--headless"])

Playwright 自带的客户端(包括其测试运行器和其他语言绑定)无需改动即可连接到引擎的 Playwright 端点:

tools/zebkit-run --headless --playwright-port=0   # prints "Playwright listening on ws://127.0.0.1:<port>/<token>"
const browser = await chromium.connect('ws://127.0.0.1:<port>/<token>');
const page = await (await browser.newContext()).newPage();

使用 CDP 的智能体和工具可以像驱动 Chrome 一样驱动 zebkit:

tools/zebkit-run --headless --remote-debugging-port=9222 https://example.com/

任何 CDP 客户端都可以连接到 http://127.0.0.1:9222。

An MCP client gets the engine's capabilities as tools from tools/zebkit-mcp, a separate program, with file access and origins denied until its operator grants them (docs/MCP.md):

ZEBKIT_DIR=<package> cargo build --release --manifest-path tools/zebkit-mcp/Cargo.toml   # or the archive's
zebkit-mcp --allow-origin https://example.com --allow-write ~/agent-files   # on standard input and output
zebkit-mcp --http 8931 --allow-origin '*'                                   # on 127.0.0.1, with a token

示例

mise example:linux:basic    # examples/window.c: a browser window
mise example:rust:window    # the same in Rust
mise example:ffmpeg         # examples/ffmpeg.cpp: a library inside the engine
(cd examples/gpui-browser && cargo run --release)   # a tabbed browser in GPUI-KIT

基准测试

与最新的稳定版 Electron 45.2.1(Chromium 156)对比,采用相同方式测量:同一测试框架、相同页面、两个执行相同操作的轻量宿主、统计每个进程、5 到 11 次轮流运行的中位数,Linux x86-64,2026-11-06 和 07(scripts/electron-compare.sh,docs/reports/electron-compare.md)。

zebkit 0.2.0 Electron 45.2.1
按应用发布时的状态,未压缩 / .tar.xz 171.2 / 59.4 MiB 260.5 / 87.1 MiB 小 34%
冷启动至首次绘制 2.3 秒 6.6 秒 少 66%
热启动至首次绘制 116 毫秒 236 毫秒 少 51%
内存,空白页面 179 MB 250 MB 少 29%
从页面到宿主再返回的一次调用,1 MiB / 16 MiB 3.3 / 58 毫秒 4.3 / 66 毫秒 少 23% / 12%
DOM,创建并布局 20,000 个元素 280 毫秒 278 毫秒 持平
JavaScript 和 DOM,十项工作负载 1139 毫秒 1193 毫秒 持平
文本布局,一本书的一章 8.1 毫秒 7.6 毫秒 持平,在噪声范围内
各自的查看器中打开一个 756 页的 PDF:CPU / 内存 0.75 秒 / 261 MB 1.6 秒 / 399 MB 少 53% / 35%
十个热门网站:加载 CPU / 内存 21.7 秒 / 4756 MB 25.2 秒 / 5060 MB 少 14% / 6%
进程数,一个页面 7 6 多一个:存储沙箱独立,与 Chrome 相同
  • 体积。 zebkit 保留了应用所使用的整个 Web 平台:HTML、CSS、包含所有 JIT 层级的 JavaScript、WebAssembly、WebGL、支持 H.264 和 AAC 的媒体、HTTP/3、PDF。
  • 速度。 Chrome 自身的配置文件引导优化能够作用于 Blink,因为 zebkit 与 Chrome 一样使用 CFI 构建;每项速度改动只有在收益大于体积代价时才会保留(docs/SIZE_AND_SPEED.md)。
  • Electron 领先之处:每个页面少一个进程,以及在十个网站中的两个——YouTube 和 The Verge——上的内存表现,这两个网站由广告和视频决定。
  • 每个数字的计算方式:docs/SIZE_AND_SPEED.md。

同样的 Playwright 命令三种方式,无头模式,中位数(docs/reports/automation-bench.md,scripts/automation-bench.sh):

zebkit 的 API,进程内 你的 Playwright 脚本,连接到 zebkit Playwright 配合 Chrome 156
locator.click() 2.0 毫秒 3.5 毫秒 33.3 毫秒
locator.fill() 2.1 毫秒 2.0 毫秒 2.9 毫秒
locator.press('Enter') 0.59 毫秒 0.98 毫秒 2.6 毫秒
locator.textContent() 0.23 毫秒 0.70 毫秒 0.81 毫秒
allTextContents() 共 200 个 0.34 毫秒 1.9 毫秒 1.9 毫秒
page.goto() 11.2 毫秒 13.0 毫秒 18.2 毫秒
page.screenshot() 12.4 毫秒 16.7 毫秒 33.8 毫秒
page.screenshot({type: 'jpeg'}) 13.9 毫秒 14.4 毫秒 32.9 毫秒
截图的像素,未编码 9.3 毫秒
  • zebkit 的 API,进程内:你的程序通过 <zebkit/automation.h>、Python 的 zebkit.automation 或 Rust 的 zebkit::automation 调用命令;它在引擎内部运行,不依赖 Node.js,也不经过协议。
  • 你的 Playwright 脚本,连接到 zebkit:Playwright 自带的 Node.js 客户端,无需改动,使用 chromium.connect() 连接到 zebkit 的端点;其测试套件(v1.64.1)在此通过了 4,046 项测试,Chrome 为 4,196 项(docs/AUTOMATION.md,第 7 节)。
  • Playwright 配合 Chrome:照常使用 Playwright,通过 CDP。
  • 为什么更快:在渲染器里一次调用,而 Playwright 的服务器要通过 CDP 进行多次往返,进程之间没有 JSON,也没有 Node.js 进程(仅 Playwright 自身就达到 175 MiB);并且点击检查所等待的帧是一次性生成的,而不是在没有显示的页面上以 60 Hz 生成。
  • 截图快 2.7 倍:它获取的新帧是一次性生成的,而不是在下一个 60 Hz 节拍时生成;PNG 使用 Up 过滤器以级别 1 压缩(耗时 2 ms,而 Chrome 的默认级别需要 13 ms,在此页面上为 59 KB 而非 40 KB);图片的共享内存是一次调用创建的,而不是一次一个缺页。12 ms 中剩下的部分是页面的帧(4 ms)、从合成器复制图片(4 ms)和编码(4 ms)。像素本身,不编码(zebkit_automation_screenshot_pixels(),zebkit 自己的),需要 9 ms。通过 CDP,zebkit 做 Chrome 所做的事:60 Hz 的帧,Chrome 的 PNG。

参考

文档

文档 涵盖内容
docs/API.md 应用程序可以调用的所有内容,支持 C、Rust、Python 和 JavaScript
docs/BUILD.md 获取 Chromium、构建、打包、测试、测量和发布;配置文件和补丁
docs/ARCHITECTURE.md 引擎是如何构建的,以及此仓库中什么是什么
docs/AUTOMATION.md 不使用 CDP 的 Playwright API:Playwright 如何工作,zebkit 做了什么替代,端点,成本
docs/MCP.md tools/zebkit-mcp:作为代理的 MCP 工具的引擎,其边界,薄弱之处
docs/SIZE_AND_SPEED.md 体积去向,削减和保留了什么,编译器与速度的对比,Electron 基线
docs/MACOS.md macOS 框架,原生和交叉构建
docs/WINDOWS.md Windows DLL,其沙箱,在 Linux 上交叉构建,移植状态
docs/SANDBOX.md 每个系统上的沙箱,以及无沙箱运行
docs/LICENSING.md zebkit 的许可证,引擎内的第三方许可证,以及发布它涉及的内容
docs/ROADMAP.md 库和引擎:需求,已完成,下一步
docs/ROADMAP_HOOKS.md 钩子:应用程序如何从原生代码决定引擎的行为
docs/UI.md zebkit.ui:引擎自己的小型 UI,它的用途和局限
bindings/rust/README.md Rust crate
bindings/golang/README.md Go 绑定:涵盖内容,使用 cgo 构建
docs/releases/ 每个版本:包含什么,测试了什么,缺少什么
docs/reports/ 体积报告,由脚本重新生成

仓库

路径 内容
upstream.lock 构建的 Chromium 版本,固定的 depot_tools,对比测量的 Electron 版本
profiles/ 作为 GN 参数的构建变体:slim.gn(产品),slim-macos.gn、slim-windows.gn、baseline.gn(Chromium 的默认值)
patches/ 对 Chromium 的更改,按 patches/series 的顺序应用
src/engine/ zebkit 自己的代码,作为 //zebkit/engine 链接到 Chromium:库、其 C API 和头文件
src/ffmpeg/ zebkit 的 FFmpeg 配置,来自 scripts/ffmpeg-config.sh
bindings/rust/、bindings/python/、bindings/golang/ Rust crate、Python 绑定和 Go 绑定
tools/ zebkit-run(打开页面,提供 CDP 或 Playwright 的端点),zebkit-js(JavaScript 应用程序)和 zebkit-mcp(作为 MCP 工具的引擎)
tests/ 针对包测试 API 每一层的程序;速度工作负载
examples/ C 中的浏览器窗口,zebkit.ui,引擎内的库,Electron 基线
scripts/ 每一步,一个脚本;mise tasks 列出简写形式

upstream/(Chromium 的树,约 30 GB),out/、dist/ 和 .cache/ 是生成的,不被跟踪。

zebkit 是 Chromium 加上一小组补丁(patches/),从源码构建。当 Chromium 缺少应用所需的某项功能时,zebkit 会直接给 Chromium 打补丁,而不是绕过它(docs/BUILD.md)。

许可证

zebkit 是自由软件,采用 GNU 宽通用公共许可证第 2.1 版或(由你选择)任何更新版本:LICENSE。任何许可证(开源或闭源)的应用都可以将其作为库使用。

该引擎包含 Chromium 及其使用的库(Blink、V8、Skia、PDFium、采用 LGPL 配置的 FFmpeg、ICU、SQLite、BoringSSL 等),每个库都遵循各自的许可证。每个发布归档都附带 LICENSE、一份说明其源码位置的 NOTICE,以及包含所有第三方许可证文本的 THIRD_PARTY_NOTICES.txt。docs/LICENSING.md 说明了分发 zebkit 的应用需要做什么。

贡献按同一许可证接受:发送拉取请求即表示你同意你的贡献按 LGPL 2.1 或更新版本授权,与 zebkit 其余部分一致。

来源:Hacker News · github.com