在 Chromium 架构中,很多开发者都会困惑一个问题:
为什么已经有 WebContents,还需要 WebView?
它们看起来都"能显示网页",但职责却完全不同。
这并不是重复设计,而是浏览器在 多进程渲染模型 + UI 框架解耦 下形成的一种经典"双层抽象架构"。
理解这两层,是掌握浏览器 UI 与内核边界的关键。
一、先说结论:一句话区别
| 层级 | 本质 | 所在进程 | 负责什么 |
|---|---|---|---|
| WebContents | 网页"逻辑实例" | Browser 进程 | 导航、生命周期、进程管理、渲染通信 |
| WebView | 网页"UI容器" | Browser 进程(Views层) | 把网页嵌进窗口,处理尺寸、焦点、输入 |
👉 WebContents = 网页的大脑
👉 WebView = 网页的身体外壳
二、浏览器不是"画网页",而是"托管网页"
现代浏览器是 多进程架构:
Browser 进程 ├─ UI 框架(Views / Aura) ├─ WebContents(页面控制器) └─ RenderWidgetHostView(渲染视图桥梁) Renderer 进程 └─ Blink + Skia(真正画页面)
注意:
Browser 进程根本不画网页,它只是"管理"和"嵌入"网页
这就是 WebContents 和 WebView 分层的根源。
三、WebContents:页面的"控制中枢"
WebContents 是 Chromium 中"一个网页实例"的完整抽象。
它不是 UI 对象,而是 浏览器对一个页面的所有控制能力的集合。
WebContents 管什么?
| 能力 | 举例 |
|---|---|
| 🌐 导航 | LoadURL()、历史记录 |
| 🧠 生命周期 | 页面创建、崩溃、恢复 |
| 🔄 进程管理 | RenderProcessHost 绑定 |
| 🧭 Frame 树 | iframe 结构 |
| 🔐 权限 | Cookie、Storage、权限请求 |
| 📡 IPC 通信 | 和 Renderer 进程消息交互 |
| 📜 JS 注入 | ExecuteJavaScript |
| 🖥 渲染视图 | 拥有 RenderWidgetHost |
所以:
没有 WebContents,就没有"页面"的概念。
但 WebContents 不关心它被显示在哪。
四、WebView:UI 框架里的"网页插槽"
WebView 是 Views 层的一个 UI 组件:
views::WebView* web_view = new views::WebView(profile); web_view->SetWebContents(web_contents);
它的职责只有一个:
把 WebContents 变成"窗口里的一块区域"
WebView 负责的事情:
| 职责 | 本质 |
|---|---|
| 📐 布局 | 大小跟随窗口变化 |
| 🎯 焦点 | 键盘输入转发 |
| 🖱 输入事件 | 鼠标/触摸事件传递 |
| 🪟 嵌入 | 加入 View 树 |
| 🧩 生命周期绑定 | View 销毁时释放 WebContents |
WebView 不懂网页逻辑,只负责:
系统事件 → RenderWidgetHostView → Renderer
五、为什么要拆两层?(核心架构原因)
🎯 原因一:同一个 WebContents 可以"换壳"
例如:
| 场景 | 说明 |
|---|---|
| 标签页拖出窗口 | WebView 变了,但 WebContents 不变 |
| TabStrip 重排 | UI变了,页面没变 |
| DevTools Dock/Undock | 同一个页面实例,不同 UI 容器 |
如果 WebContents 自己是 UI 组件,这些操作就会变得极其复杂。
🎯 原因二:支持无 UI 的页面
有些页面根本不显示:
-
Service Worker
-
Background Page
-
Prerender 页面
-
Print Preview
它们需要 WebContents,但不需要 WebView
🎯 原因三:跨平台 UI 解耦
Chromium 支持:
-
Windows Views
-
Mac Cocoa
-
Android WebView
-
ChromeOS
如果 WebContents 直接依赖 UI,就无法跨平台。
六、底层关系图(关键)
WebView (UI) ↓ RenderWidgetHostView (桥梁) ↓ RenderWidgetHost ↓ Renderer 进程
而 WebContents 在旁边:
WebContents ├─ NavigationController ├─ FrameTree ├─ RenderViewHost └─ RenderWidgetHost
👉 WebView 只是把 RenderWidgetHostView "插进界面"
七、真实场景对比
场景:新建一个 Tab
std::unique_ptr<WebContents> contents = WebContents::Create(params); views::WebView* view = new views::WebView(profile); view->SetWebContents(contents.get());
| 对象 | 作用 |
|---|---|
| WebContents | 创建页面逻辑 |
| WebView | 让它显示在 UI |
场景:Tab 被关闭
-
WebView 被移除 View 树
-
触发 WebContents 销毁
-
Renderer 进程可能被回收
场景:页面崩溃
-
WebContents 收到进程崩溃
-
UI 上 WebView 显示"页面崩溃"提示
-
WebView 没崩,只是内容不可用了
八、这套设计的工程价值
| 价值 | 体现 |
|---|---|
| 解耦 UI 与 内核 | UI 重构不影响内核 |
| 支持多进程安全模型 | WebContents 管进程 |
| 支持页面复用 | Tab 拖拽 |
| 支持后台页面 | 无 UI 运行 |
| 易扩展 | DevTools、插件、预渲染 |
九、常见误区
❌ "WebView 就是网页"
✔️ 错,它只是"显示容器"
❌ "WebContents 就是视图"
✔️ 错,它是页面的控制逻辑
十、终极理解模型
WebContents = 浏览器对"网页进程"的控制权
WebView = 操作系统窗口系统里的"一个洞"
浏览器真正做的不是画网页,而是:
创建一个网页进程 → 管理它 → 在窗口里给它开个洞 → 把像素流贴上来
这就是现代浏览器的本质。