chromium: ui控件的实现

在chromium浏览器中,有两种显示部分。一个是显示网页的部分我们叫它embeded内容,用webui实现。其他部分就是非webui,比如菜单栏,url输入框,前进后退按钮。

一、WebUI

WebUI 是指:

一套专门用于构建基于 HTML/CSS/JS 的内部网页界面系统,运行在 renderer process 中,但与浏览器核心交互。

示例:

  • chrome://settings/ 设置页面

  • chrome://bookmarks/ 书签管理器

  • chrome://flags/ 实验功能开关界面

这些页面虽然运行在浏览器内部,但其实是用 HTML/CSS/JS 构建的,就像普通网页一样,但具有与浏览器进程通信的特权。使用 WebUIControllerMojo 等机制桥接 C++ ↔ JS。

典型路径:

src/chrome/browser/ui/webui/       // 控制器类(C++)
src/chrome/browser/resources/      // HTML/JS/CSS 资源

二、Chromium 的非 WebUI 界面:Views 系统

Chromium 浏览器自身的 UI(浏览器框架壳)是用一个名叫 Views 的 C++ UI 框架开发的:

它包括:

  • 地址栏(Omnibox)

  • 返回 / 前进 / 刷新 / Home 按钮

  • 工具栏、标签栏(TabStrip)

  • 浏览器主菜单(... 下拉菜单)

实现位置:

src/ui/views/                         # Views 基础控件
src/chrome/browser/ui/views/         # 浏览器的主界面 UI 实现
src/chrome/browser/ui/views/toolbar/ # 工具栏相关控件
src/chrome/browser/ui/views/location_bar/ # 地址栏控件

  • 可以参考代码:
# 地址栏控件:
chrome/browser/ui/views/location_bar/location_bar_view.cc

# 工具栏控件:
chrome/browser/ui/views/toolbar/toolbar_view.cc

# 主窗口:
chrome/browser/ui/views/frame/browser_view.cc

🔹1. 为什么 Views 能做到跨平台?

Chromium 使用了一整套自研的 平台抽象 UI 框架,包括:

层次作用框架 / 组件
UI 控件层通用 UI 控件(按钮、输入框、列表等)ui/views
绘图层负责绘制矢量图、文字、位图等Skia(跨平台绘图引擎)
平台适配层提供原生窗口、输入事件、光标等ui/platform_window/ + base/message_loop/

✅ 控件定义是“平台无关”的(统一用 C++ 实现):

比如 Label, Button, Textfield,在 ui/views/controls/ 目录下是统一的 C++ 类,不依赖平台 GUI 框架。

✅ 但绘图和窗口管理是“平台相关”的:

  • macOS → 使用 CoreAnimation/NSWindow + Skia

  • Windows → 使用 HWND + Skia + Direct2D/GDI

  • Linux → 使用 X11/Wayland + Skia

Skia 是 Google 自研的跨平台 2D 绘图库,Android、Chrome 都用它。

🔹2. 合成线程影响所有 UI,是为什么?

Chromium 的 整个图形系统(包括 WebContents、Views)都依赖 Compositor 合成输出!

Chromium 图形渲染总流程:

所以不论是 Web 页面还是原生 UI 控件,它们都会生成 Skia draw ops,交给合成线程合成。如果你关闭了合成线程,整个画面“卡在了中间过程”,什么都无法显示,包括地址栏、菜单栏等。

✅ Views 虽然不使用 HTML,但它仍然要“画出来”,靠的是 Skia → Compositor → GPU。


🔹3. 菜单栏、按钮控件在不同平台如何实现?

控件逻辑是平台通用的:

  • 所有按钮、菜单栏逻辑都在 ui/views 里用 C++ 定义

  • 例如 BackButtonToolbarButtonAppMenuButton

但平台适配部分会使用不同“Widget Host”

例如:

平台顶层窗口实现控件绘图方式
WindowsHWND + WindowImplSkia 渲染到 Direct2D 或 GDI
macOSNSWindow + NSViewSkia 渲染成 CALayer,挂到 NSView 上
LinuxX11Window / WaylandWindowSkia 绘图输出到 X11/Wayland surface

Chromium 使用 views::Widget 类统一封装窗口和控件树,但其底层用平台特定代码来适配系统窗口管理器。


📁 几个关键源码位置

功能路径说明
Views 控件入口ui/views/所有 UI 控件定义
主窗口chrome/browser/ui/views/frame/browser_view.cc浏览器主 UI 框架
平台窗口接口ui/platform_window/跨平台窗口抽象
渲染层桥接ui/compositor/Compositor 层的组织逻辑
Skia 调用cc/paint/ui/gfx/SkCanvas、SkImage 的封装

🧠 延伸理解:Skia + Compositor 是 Chromium UI 的大一统方案

Chromium 不像传统原生应用用 Qt/WinAPI/AppKit,它把所有东西(HTML + UI 控件)都统一到 Skia 渲染 → Compositor 合成 这一条链上。

这样做的好处:

  • 所有 UI 都支持动画、透明度、阴影

  • 跨平台一致性强

  • 后期统一 GPU 加速、硬件合成优化

代价:

  • 对图形系统耦合度高,合成线程关闭会导致 UI 全挂

  • 初学者难以理解 “明明不是网页,为何也渲染不出来”


✅ 总结

问题答案
Views 是如何跨平台的?用统一 C++ 控件 + Skia 绘图 + 平台窗口适配器实现
合成线程关闭为什么整个 UI 都挂了?Chromium 的原生控件也依赖 Skia + Compositor 绘图
菜单栏控件是怎么显示的?控件逻辑统一,绘图输出在 Skia,由平台窗口包裹(如 NSWindow/HWND)显示

三、BackButton实现讲解

BackButton 是 Chromium 浏览器顶部工具栏(toolbar)左上角那个“返回”箭头按钮,它是 Chromium UI 框架 Views 中的一个控件,由 C++ 代码实现和绘制,不依赖 HTML/CSS/JS


🧩 一、BackButton 的类层次与路径

🔹 类结构:


BackButton : public ToolbarButton
            → LabelButton
              → Button
                → View

🔍 核心路径:


chrome/browser/ui/views/toolbar/back_button.cc
chrome/browser/ui/views/toolbar/back_button.h

这是它的类定义和实现文件。BackButton 继承自 ToolbarButton,后者又是 Chromium 中一个可点击、可绘图的按钮控件。


🖌️ 二、绘制过程大致流程

BackButton 的绘制是通过 C++ 调用 Skia API(SkCanvas)进行的,整个过程大致如下:

1. 在主窗口中创建 BackButton 实例

BrowserView 初始化过程中:


back_button_ = toolbar_->AddChildView(std::make_unique<BackButton>());

2. BackButton 会使用 Vector Icon 绘制箭头图标

back_button.cc 中:


SetImageModel(ButtonState::STATE_NORMAL,
              ui::ImageModel::FromVectorIcon(kBackArrowIcon));

  • kBackArrowIcon 是一个内置的矢量图标,定义在 chrome/app/vector_icons/ 中的 .icon 文件(其实是 SVG 简化格式)。

  • 它会被编译为 C++ 中的 path 描述,最终通过 Skia 绘制。

const gfx::VectorIcon kBackArrowIcon = {
    kBackArrowPath,  // SkPath 描述路径
    base::size(kBackArrowPath),
    gfx::VectorIconId::BACK_ARROW,
};

Chromium 有自己的矢量图标处理工具链,支持多种状态(正常 / hover / 按下等)切换不同颜色或透明度。


🎨 三、绘图函数调用栈(Skia)

最终,BackButton 的绘图操作会通过:


ToolbarButton::OnPaint()
 → LabelButton::OnPaint()
   → Button::PaintButtonContents()
     → PaintIcon()
       → ImageSkia::Draw()
         → SkCanvas::drawPath() / drawBitmap()

💡 关键点:

  • 实际绘图发生在 Chromium 的绘图层 ui/gfx/ 下,使用 SkCanvas。

  • 所有控件绘图操作会组成 draw op 列表(DisplayItemList),交给 Compositor 合成。


🧪 四、如何定制或观察 BackButton 绘图?

你可以:

  1. back_button.cc 里设置自定义图标:

    
    
    SetImageModel(STATE_NORMAL,
      ui::ImageModel::FromImage(gfx::CreateVectorIcon(kMyCustomIcon, 16, SK_ColorRED)));
    

  2. 在 OnPaint() 添加 Skia 调试输出:

    
    
    void BackButton::OnPaint(gfx::Canvas* canvas) {
      LOG(ERROR) << "Painting BackButton!";
      ToolbarButton::OnPaint(canvas);
    }
    

  3. 调试绘图流程:

    • 启用 --enable-skia-deferred-display-list

    • 使用 Chromium 的 skia_debugger 工具查看画面构建过程


💬 五、附加细节:响应事件、动画等

  • 鼠标悬浮/点击效果使用 InkDrop 动画系统(Material Design 风格)

  • 可访问性(Accessibility)支持通过 View::GetAccessibleNodeData() 提供名称与用途

  • 按钮点击事件最终会调用:

    
    
    chrome::ExecuteCommand(browser_, IDC_BACK);
    

    用于执行历史后退导航


✅ 总结

项目实现方式
BackButton 类型C++ 类 BackButton,继承自 ToolbarButton
图标绘制使用矢量图标 kBackArrowIcon,通过 Skia 绘制
渲染系统Skia + Views + Compositor
不依赖HTML、CSS、JS、WebView(完全原生)
代码路径chrome/browser/ui/views/toolbar/back_button.cc

四、自定义控件实现

在 Chromium 的 Views 框架中实现一个自定义控件:“前进 + 后退”组合按钮(ForwardBackButton),功能如下:

🧩 功能设计:

  • 左侧是“←”返回按钮,右侧是“→”前进按钮

  • 两个图标合在一个控件中

  • 鼠标点击左右区域分别触发对应命令:IDC_BACKIDC_FORWARD

  • 布局与绘制完全使用 Views + Skia


📁 目录建议:

你可以将这个控件放在:

chrome/browser/ui/views/toolbar/forward_back_button.{h,cc}


✅ 1. forward_back_button.h


#pragma once

#include "ui/views/view.h"
#include "ui/views/controls/button/button.h"

class Browser;

class ForwardBackButton : public views::View, public views::ButtonListener {
 public:
  explicit ForwardBackButton(Browser* browser);

  // views::View:
  void OnPaint(gfx::Canvas* canvas) override;
  gfx::Size CalculatePreferredSize() const override;
  void Layout() override;

  // views::ButtonListener:
  void ButtonPressed(views::Button* sender, const ui::Event& event) override;

 private:
  raw_ptr<Browser> browser_;
  raw_ptr<views::Button> back_button_;
  raw_ptr<views::Button> forward_button_;
};


✅ 2. forward_back_button.cc


#include "chrome/browser/ui/views/toolbar/forward_back_button.h"

#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/ui/browser_commands.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/vector_icon_utils.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/layout/box_layout.h"

ForwardBackButton::ForwardBackButton(Browser* browser) : browser_(browser) {
  auto back = std::make_unique<views::ImageButton>(this);
  back->SetImageModel(views::Button::STATE_NORMAL,
                      ui::ImageModel::FromVectorIcon(kBackArrowIcon));
  back->SetAccessibleName(u"Back");
  back_button_ = AddChildView(std::move(back));

  auto forward = std::make_unique<views::ImageButton>(this);
  forward->SetImageModel(views::Button::STATE_NORMAL,
                         ui::ImageModel::FromVectorIcon(kForwardArrowIcon));
  forward->SetAccessibleName(u"Forward");
  forward_button_ = AddChildView(std::move(forward));

  SetLayoutManager(std::make_unique<views::BoxLayout>(
      views::BoxLayout::Orientation::kHorizontal, gfx::Insets(), 2));
}

void ForwardBackButton::OnPaint(gfx::Canvas* canvas) {
  View::OnPaint(canvas);  // 默认背景绘制
}

gfx::Size ForwardBackButton::CalculatePreferredSize() const {
  gfx::Size back_size = back_button_->GetPreferredSize();
  gfx::Size forward_size = forward_button_->GetPreferredSize();
  return gfx::Size(back_size.width() + forward_size.width() + 4,
                   std::max(back_size.height(), forward_size.height()));
}

void ForwardBackButton::Layout() {
  back_button_->SetBounds(0, 0,
      back_button_->GetPreferredSize().width(), height());
  forward_button_->SetBounds(
      back_button_->bounds().right() + 4, 0,
      forward_button_->GetPreferredSize().width(), height());
}

void ForwardBackButton::ButtonPressed(views::Button* sender,
                                      const ui::Event& event) {
  if (sender == back_button_) {
    chrome::ExecuteCommand(browser_, IDC_BACK);
  } else if (sender == forward_button_) {
    chrome::ExecuteCommand(browser_, IDC_FORWARD);
  }
}


🚀 3. 如何使用它?

打开 browser_view.cctoolbar_view.cc 中你希望插入的位置,比如:

#include "chrome/browser/ui/views/toolbar/forward_back_button.h"

...

forward_back_button_ = AddChildView(std::make_unique<ForwardBackButton>(browser_));

🎨 4. 可拓展性建议

  • 添加 hover 动画(InkDrop)

  • 支持禁用状态(不能前进/后退时灰掉)

  • 支持长按弹出历史记录菜单(如 Chrome 原始行为)

  • 支持 Mac 风格圆角按钮风格


✅ 总结

内容说明
框架使用 Views 实现组合控件
图标使用 VectorIcon
事件响应使用 ButtonListener
可移植性不依赖平台代码,跨平台支持
插入方式在 Toolbar 中添加子 View

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值