机智的 WebUI

机智的 WebUI

Chromium 是一个浏览器。

除了网页使用到了诸如 HTML 等 Web 技术外,其余组件,像按钮、窗体等,都是 Native。

很多人,包括我,在最开始的时候都是这么想的。

其实不是。

有些复杂 UI,同样是通过 Web 技术实现的。比如大家最常用的:chrome://settings/。

看起来不像,但它的确是个 Web 页面。你不妨用 F12 打开调试栏看看。

当然,这不是常规 B/S 模式下的网页,而是 Chromium 内在支持的一种,借助 Web 技术快速实现复杂用户交互的手段,即 WebUI。

1. WebUI 概念

按照 Chromium 官方文档的说法:

“WebUI” is a term used to loosely describe parts of Chrome’s UI implemented with web technologies (i.e. HTML, CSS, JavaScript).

“WebUI” 是一个术语,用于宽泛地描述使用 Web 技术(即 HTML、CSS、JavaScript)实现的 Chrome UI 部分。

构建 UI 通常有两种方式,Native 或 Web。Native 就是借助 WinForms、QT 这种,偏底层,性能好;Web 则更适合敏捷开发,灵活,使用效率高。

Chromium 作为一个典型的 Native 应用,内部复杂的 UI 却用 Web 实现。这种 Hybrid 的方案,无疑兼顾了两者之长。

可以这么说,WebUI 就是 Chromium 内置的“网页”,通过类似网页的方式加载,但内容完全来自本地,通信也是和 Chromium 自身。

几个常见的使用 WebUI 的例子有:

Settings (chrome://settings🔗)

History (chrome://history🔗)

Downloads (chrome://downloads🔗)

2. 架构设计

虽说 WebUI 使用到了 Web 技术,但与普通网页又存在诸多不同。最明显的,就是“高权限”。

WebUIs are granted super powers so that they can manage Chrome itself.

WebUI 被赋予了超能力,以便它们可以管理 Chrome 本身。

WebUI 可以与浏览器内部敏感的,比如隐私和安全服务通信。

这需要一系列底层机制的支持:

允许 Render 进程加载形如 chrome:// 的 URL

允许通过 CallJavascriptFunction()🔗 执行任意 JavaScript

允许使用 chrome.send()🔗 实现 Render 和 Browser 的通信

忽略有关显示图像或执行 JavaScript 的内容设置(Content Settings)

从架构角度,WebUI 可分为三层:前端层;WebUI Controller 层;通信层。

前端层(HTML/CSS/JS)

和普通 Web 页面类似,运行在 Blink 渲染引擎中

WebUI Controller(C++ 层)

每个 WebUI 页面对应一个 WebUIController 派生类

负责注册资源(HTML、JS、Image 等),定义与前端通信的消息通道

通信层(Mojo IPC / WebUIMessageHandler)

mojom + Mojo,基于强类型接口的跨进程通信,更加高效、安全,连接不同进程,并将用户意图传导给浏览器

一个 WebUI 的加载流程大致如下:

用户输入 chrome://example/

WebUIControllerFactory 根据 URL 匹配到对应的 WebUIController

Controller 通过 content::WebUIDataSource 提供前端资源

前端加载完成后,通过 Mojo 或 WebUIMessageHandler 向 C++ 发送请求

浏览器端执行(如获取配置、调用系统 API),再把结果返回前端

和一个 B/S 架构的网页很像,只不过 Server 变成了浏览器。

3. 如何使用

以 chrome://hello 为例,看一个极简的 WebUI 是如何实现的。

3.1. 定义 WebUI Controller

hello_ui.hclass HelloUI : public ui::MojoWebUIController { public: explicit HelloUI(content::WebUI* web_ui); ~HelloUI() override;};// hello_ui.ccHelloUI::HelloUI(content::WebUI* web_ui) : ui::MojoWebUIController(web_ui, true /* enable Mojo */) { auto source = content::WebUIDataSource::Create("hello"); source->AddResourcePath("hello.js", IDR_HELLO_JS); source->SetDefaultResource(IDR_HELLO_HTML); content::WebUIDataSource::Add(web_ui->GetWebContents()->GetBrowserContext(), source);}

3.2. 定义 Mojo 接口

hello.mojommodule hello.mojom;

interface HelloPageHandler { SayHello() => (string response);};

生成后,会得到对应的 C++ 和 JS 绑定。

3.3. 实现 Handler

hello_page_handler_impl.ccclass HelloPageHandlerImpl : public hello::mojom::HelloPageHandler { public: void SayHello(SayHelloCallback callback) override { std::move(callback).Run("Hello from C++!"); }};

3.4. 前端调用

hello.html

// hello.js import {HelloPageHandler} from 'hello.mojom-webui.js'; const handler= HelloPageHandler.getRemote(); handler.sayHello().then(response => {document.getElementById('msg').innerText = response; });

访问 chrome://hello,就能看到 C++ 返回的 “Hello from C++!”。

4. 注意事项

因为 WebUI 有比较高的权限,因此安全问题是重中之重。

因此从设计上,

WebUI 无法嵌入 HTTP/HTTPS 资源

WebUI 不能发起 HTTP/HTTPS 请求

在极其少数的情况下,WebUI 确实需要包含 Web 内容,安全的办法是使用