从 GRIT 到 WebUI:Chromium 内置资源加载与前端展示的完整链路解析

一、前言

在 Chromium 内核开发过程中,国际化字符串的加载与 WebUI 内置页面的展示,是两个经常被提及但又容易被忽视的核心环节。

  • 一方面,GRIT(Google Resource and Internationalization Tool)承担了整个字符串资源编译、打包和加载的任务,它决定了开发者在 C++ 代码或者 WebUI 前端中看到的 IDS_XXX 字符串是如何被替换成实际文字的。

  • 另一方面,WebUI 内置页(如 chrome://settings/chrome://newtab/)的展示机制,涉及到 C++ 后端和前端 HTML/JS 的对接,GRIT 中的资源如何最终呈现在用户眼前,也是一个完整的链路。

本篇文章将深入剖析 GRIT 的编译链路、运行时加载机制、多语言切换原理、资源 ID 分配 ,再结合 WebUI 内置页的对接机制,展示一个完整的前端展示流程。

阅读完这篇文章,你将能够回答:

  1. .grd 文件和 .xtb 文件有什么区别?

  2. .grd 如何编译成 .pak 文件,运行时又如何被加载?

  3. 多语言切换的机制是怎样的?

  4. WebUI 内置页如何与这些资源对接?

  5. 一个完整的字符串从 定义 → 编译 → 加载 → 前端展示 的链路是什么?


二、GRIT 资源体系介绍

1. 什么是 GRIT

GRIT,全称 Google Resource and Internationalization Tool,是 Chromium 用来管理字符串、图片、图标等 UI 资源的工具。它的主要职责是:

  • 资源定义 :通过 .grd 文件声明一组资源(通常是字符串)。

  • 国际化支持 :通过 .xtb 文件为不同语言提供翻译。

  • 资源编译 :在构建过程中生成 .h 头文件和 .pak 打包文件。

  • 运行时加载 :通过 ui::ResourceBundlel10n_util API 在代码中加载资源。


2. .grd 文件

.grd 文件是资源的"清单文件",开发者在其中定义需要本地化的字符串,例如:

复制代码
<message name="IDS_BACKGROUND_APP_INSTALLED_BALLOON_BODY" desc="Balloon content"> <ph name="APP_NAME">$1<ex>Background App</ex></ph> will launch at system startup and continue to run in the background even once you've closed all other <ph name="PRODUCT_NAME">$2<ex>Google Chrome</ex></ph> windows. </message> 

特点:

  • name 定义了字符串 ID(如 IDS_BACKGROUND_APP_INSTALLED_BALLOON_BODY)。

  • desc 用于描述字符串用途,方便翻译。

  • <ph> 表示参数占位符。


3. .xtb 文件

.xtb 文件是 翻译文件 ,用于为 .grd 中的字符串提供多语言版本。

例如 en-US.xtbzh-CN.xtb,它们内部存放着 <translation> 节点,对应不同语言的翻译。


4. .pak 文件

在编译阶段,GRIT 工具会把 .grd + .xtb 合并,最终生成 .pak 文件。

.pak 文件是一个二进制资源包,存放了 ID → 文本 的映射关系。

例如:

  • IDS_BACKGROUND_APP_INSTALLED_BALLOON_BODY (id=12345)"某某应用将在系统启动时运行"

三、GRIT 的编译流程

我们来看字符串是如何从 .grd 最终进入到 .pak 的。

1. .grd.h

在 GN 构建过程中,.grd 文件会通过 grit.py 工具转换成 C++ 头文件,生成的 .h 文件包含一系列 #define 宏:

复制代码
#define IDS_BACKGROUND_APP_INSTALLED_BALLOON_BODY 12345 

这样,C++ 代码中使用 IDS_BACKGROUND_APP_INSTALLED_BALLOON_BODY 时,实际上就是在使用 整型 ID


2. .grd + .xtb.pak

构建时,GRIT 工具会:

  • 读取 .grd 中定义的 ID。

  • 根据语言选择,加载对应 .xtb 文件翻译。

  • 生成 .pak 文件,将 ID 和对应的翻译文本打包进去。

例如:

复制代码
pak 文件 ├── 12345 → "应用将在系统启动时运行" (中文) ├── 12345 → "The app will launch at system startup" (英文) 

3. .pak 文件的安装位置

不同平台下,Chromium 会把 .pak 文件放在:

  • Windows: chrome.pak / resources.pak

  • Linux: out/Default/locales/zh-CN.pak

  • macOS: Chromium.app/Contents/Resources/zh-CN.pak


四、运行时加载机制

1. ui::ResourceBundle

运行时,Chromium 通过 ui::ResourceBundle 加载 .pak 文件。

复制代码
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); rb.AddDataPackFromPath(locale_path, ui::SCALE_FACTOR_NONE); 
  • AddDataPackFromPath 会把 .pak 文件加载到内存。

  • 内部使用一个 ResourceMap 存放 ID → 文本的映射。


2. l10n_util API

开发者在 C++ 代码中使用 l10n_util::GetStringUTF16(id) 来获取字符串:

复制代码
base::string16 text = l10n_util::GetStringUTF16( IDS_BACKGROUND_APP_INSTALLED_BALLOON_BODY); 

流程是:

  • GetStringUTF16 → 查询 ui::ResourceBundle → 找到 ID 对应的文本。

  • 返回 std::u16string,供 UI 展示。


3. 多语言切换

Chromium 的多语言切换依赖 Locale ,即系统语言或者浏览器启动参数:--lang=zh-CN

  • 启动时,ResourceBundle::InitSharedInstanceWithLocale 会选择对应语言的 .pak 文件。

  • 当用户在设置里切换语言时,浏览器需要重启,重新加载另一套 .pak


4. 资源 ID 的编译期分配

值得注意的是,ID 是在编译期固定的整数

例如:

复制代码
#define IDS_HELLO_WORLD 6001 

无论语言怎么切换,IDS_HELLO_WORLD 始终是 6001,只有 .pak 文件里绑定的翻译会变化。


五、WebUI 内置页的前端对接

字符串加载只是第一步,最终还需要在 WebUI 页里展示。

1. 内置页 WebUI 概念

  • 内置页的访问方式是 chrome://xxx/

  • 底层由 WebUIController 负责加载页面。

  • 前端通常是 HTML + JS + CSS,存放在 resources/ 目录。


2. 字符串注入

C++ 后端会把 .pak 中的字符串传递给前端。常见方式是 LoadTimeData

复制代码
webui::LocalizedString localized_strings[] = { {"appInstalledBalloonBody", IDS_BACKGROUND_APP_INSTALLED_BALLOON_BODY}, }; AddLocalizedStrings(source, localized_strings); 

这样,前端 HTML 就能通过:

复制代码
const text = loadTimeData.getString('appInstalledBalloonBody'); 

拿到对应的字符串。


3. 资源绑定流程

整体链路如下:

  1. .grd 定义字符串。

  2. 编译生成 .pak

  3. 运行时 ResourceBundle 加载 .pak

  4. WebUIDataSourceAddLocalizedStrings 注入到前端。

  5. 前端 loadTimeData.getString() 使用。


4. 实际案例

比如 新标签页(New Tab Page, NTP)

  • C++ 端:

    复制代码
    source->AddLocalizedString("title", IDS_NEW_TAB_TITLE); 
  • 前端端:

    复制代码
    <h1 id="title"></h1> <script> document.getElementById("title").textContent = loadTimeData.getString('title'); </script> 

这样,最终 <h1> 会显示 "新标签页""New Tab"


六、GRIT + WebUI 的完整链路

结合上面的分析,我们得到一个完整链路:

  1. 开发者定义

    • .grd 文件中声明字符串 ID。
  2. 构建编译

    • .grd 生成 .h,定义整数 ID。

    • .xtb 翻译合并,生成 .pak

  3. 运行时加载

    • ResourceBundle 加载 .pak

    • l10n_util 根据 ID 获取字符串。

  4. WebUI 注入

    • C++ 后端使用 AddLocalizedStrings 注入前端。

    • 前端用 loadTimeData 获取并展示。


七、对比总结

阶段 输入 输出 工具/接口
编译前 .grd / .xtb .h / .pak grit.py
编译后 .pak ID → 文本 ResourceBundle
运行时 ID 文本 l10n_util::GetStringUTF16
WebUI 文本 页面展示 loadTimeData.getString

八、结语

GRIT 与 WebUI 的结合,是 Chromium 国际化 + 内置页面展示 的关键路径。

  • .grd 文件解决了资源声明与 ID 分配;

  • .xtb 文件提供了多语言翻译支持;

  • .pak 文件把不同语言打包成可分发资源;

  • ui::ResourceBundle 在运行时统一加载;

  • WebUI 内置页通过 AddLocalizedStringsloadTimeData 实现最终展示。

掌握这条链路后,你不仅能理解 字符串是如何出现在页面上的,还能在调试 WebUI 内置页时快速定位问题(如字符串丢失、多语言未生效等)。