Chrome学习小记3:基于Chrome Views框架创建最小示例窗口A(从Example分析开始)

前言

​ 笔者经过一系列复杂的调研,最后折腾出来一个比较简单的demo样例,这里把示例拉出来,方便我们研究和调试Chrome View代码调试,从而进一步理解Chrome View框架到底是如何工作的。涉及到了哪些方面,哪些内容是我们值得注意的。

回顾

​ 如果您还没有看笔者的上一篇博客,麻烦您现在去看一下,我们的第一步是添加一个新的构建单元进来,生成我们自己的样例出来。笔者这里贴一下链接:

CSDN:构建一个全新的编译单元

自己的个人博客地址:Chrome学习小记2:GN构建系统小记|Hello World

​ 笔者这里的做法是在Chrome的源码目录下的ui/views/examples新建了一个wallpaper_demo文件夹,里面放上一个main.cc 这个C++文件,具体做什么,是下一篇博客我才会说明的。这里暂时这么叫(笑)

​ 我们先写下最基本的构建脚本

executable("wallpaper_demo") {
  testonly = true
  sources = [
    "wallpaper_example/main.cc",
  ]

  if (is_win) {
    configs += [ "//build/config/win:windowed" ]
    configs -= [ "//build/config/win:console" ]
  }
}

​ 我们放到ui/views/examples下的BUILD.gn,添加一段这个代码。这段代码实际上就是声明了咱们的wallpaper_demo可执行文件的源代码依赖于wallpaper_demo文件夹下的main.cc。很显然,这个时候,咱们只需要完成main.cc的内容就好。

检查一下我们的构建是否顺利

​ 非常简单,只需要我们在main.cc下写下一个最简单的Windows GUI程序即可

#include <Windows.h>	// for wWinMain and MessageBox

int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR, int)
{
    // Chrome/C++ 推介我们不要书写NULL,而是使用更加安全的nullptr
    MessageBox(nullptr, "This is A MessageBox", "This is A MessageBox", 0);
    return 0;
}

​ 接下来,就是将新添加的构建项使能进来。也就是

gn gen out/Default

​ 这个操作在上一篇博客说了,GN生成ninja构建目录在我们的当前目录下的out/Default下。

autoninja -C out/Default wallpaper_demo

​ 这个操作会让我们重点生成咱们的wallpaper_demo,也就是我们构建的一个最小的消息框显示的demo。如果一切顺利,我们就会看到一个小的消息框出现了。

重要的一步:参考views_examples来创建我们自己的demo

​ 这一步才是我们的核心。前面的工作已经搭建完成,构建的基本框架也搞好了。Google有一些样例工程给我们进行参考,笔者编写demo的出发点也是从这里开始的。

​ 在那之前,我们先看一看Google Chrome是如何使用Views来完成一个窗口的创建,各个子系统之间是如何协调工作的。下面的代码笔者已经做过很大幅度的化简,偏特化的前置条件为:我们使用的是Windows平台的编译 + Aura 作为一个跨平台的窗口系统抽象层的话,咱们的代码实际上可以被化简为如下的流程。(请注意,我合并了ui/views/example_main.ccui/views/examples_main_proc.cc的代码)

// 使用 LazyInstance 模板来创建一个全局的、在退出时析构(DestructorAtExit)的
// base::TestDiscardableMemoryAllocator 实例。这种做法可以保证延迟构建(lazy init)
// 且程序退出时能够正确析构。
base::LazyInstance<base::TestDiscardableMemoryAllocator>::DestructorAtExit
    g_discardable_memory_allocator = LAZY_INSTANCE_INITIALIZER;

bool g_initialized_once = false;  // 标识是否已经做过一次初始化,防止重复初始化

int main(int argc, char** argv)
{
  // 初始化命令行解析系统,将 main 的 argv/argc 注册给 base::CommandLine
  base::CommandLine::Init(argc, argv);

  // 接下来基于 TestTimeouts 的时间限制测试环境里某些超时值
  // 需要用到 TestTimeouts 中定义的默认超时时间
  TestTimeouts::Initialize();

  // AtExitManager 用来管理程序退出时需要调用的析构 / 清理回调
  base::AtExitManager at_exit;
    
  // 在 Windows 上,某些 UI 或 COM/OLE 操作需要进行 COM 初始化
  // ScopedOleInitializer RAII 风格,确保在作用域结束时清理
  ui::ScopedOleInitializer ole_initializer;
    
  // 获取当前进程的命令行对象指针
  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
  
  // 用于测试的辅助平台接口,用于无头/自动化测试中模拟/查询辅助功能 (accessibility) 行为
  ui::AXPlatformForTest ax_platform;
    
  // 禁用 Direct Composition。这是为了绕开一个问题:
  // InProcessContextFactory 不兼容 Direct Composition,导致窗口没有被渲染。
  // 相关问题见 http://crbug.com/936249(Chrome bug)  
  command_line->AppendSwitch(switches::kDisableDirectComposition);

  // 初始化 FeatureList,用于启用/禁用某些实验性功能
  // 从命令行开关中读取要启用和禁用的 features
  base::FeatureList::InitInstance(
      command_line->GetSwitchValueASCII(switches::kEnableFeatures),
      command_line->GetSwitchValueASCII(switches::kDisableFeatures));
  
  // 只做一次的全局初始化部分
  if (!g_initialized_once) {
    // 初始化 Mojo 核心,用于进程间通信(IPC)等
    mojo::core::Init();

    // 初始化 OpenGL 或类似 GPU 后端的一次性设置
    gl::init::InitializeGLOneOff(
        /*gpu_preference=*/gl::GpuPreference::kDefault);

    // 初始化 ICU 国际化库(字符集、时区、数字格式等本地化支持)
    base::i18n::InitializeICU();

    // 注册路径提供者,这样 base::PathService / UI 路径之类的能正确解析资源路径
    ui::RegisterPathProvider();

    // 将可丢弃内存(DiscardableMemory)分配器设为全局实例,
    // 用上面定义的 lazy 实例 g_discardable_memory_allocator
    base::DiscardableMemoryAllocator::SetInstance(
        g_discardable_memory_allocator.Pointer());

    // 初始化字体子系统,比如加载系统字体、建立字体缓存等
    gfx::InitializeFonts();

    // 标记已初始化状态
    g_initialized_once = true;
  }  
  
  // 创建一个 TaskEnvironment(任务环境),它会管理线程任务/消息循环/定时器等
  // 在 UI 类型的主线程模式下运行,因为 Viz(渲染合成系统)依赖于此来正确销毁/清理资源
  base::test::TaskEnvironment task_environment(
      base::test::TaskEnvironment::MainThreadType::UI);

  // 创建 ContextFactories,用于 UI 渲染上下文/Compositor 的创建。
  // 在创建任何 Compositor(合成器)之前必须先有这个
  auto context_factories =
      std::make_unique<ui::TestContextFactories>(under_test,
                                                 /*output_to_window=*/true);  
    
  // 资源包 (pak) 文件路径,用于 UI 测试里的资源加载
  base::FilePath ui_test_pak_path;
  CHECK(base::PathService::Get(ui::UI_TEST_PAK, &ui_test_pak_path));
  ui::ResourceBundle::InitSharedInstanceWithPakPath(ui_test_pak_path);
  
  // (省略与资源加载有关的初始化细节,我们稍后的代码不涉及这个内容)

  // -------------------------------------------
  
  // 创建 aura 环境实例。 aura::Env 是管理窗口环境、输入、显示屏幕等跨平台 UI 环境的核心类
  std::unique_ptr<aura::Env> env = aura::Env::CreateInstance(); 
  // 设置上下文/合成上下文(context factory),使 aura 环境知道如何创建 compositor/绘制上下文
  aura::Env::GetInstance()->set_context_factory(
      context_factories->GetContextFactory());  
  
  // 初始化输入法系统(Input Method)用于测试环境(键盘/鼠标/IME 模拟等)
  ui::InitializeInputMethodForTesting();
  
  // 创建一个 DesktopTestViewsDelegate,用于替代正常运行时的 ViewsDelegate
  // 在测试中用以控制窗口/控件/Views行为
  views::DesktopTestViewsDelegate views_delegate;
  
  // WMState 管理窗口管理器状态,在桌面环境中相关窗口规则/行为会用到
  wm::WMState wm_state;
  
  // 创建屏幕对象,代表显示器/屏幕信息,用于视图系统查询显示屏幕尺寸/分辨率/变换等
  std::unique_ptr<display::Screen> desktop_screen =
        views::CreateDesktopScreen();
  
  // RunLoop 是主事件循环/消息循环,用来跑 UI 消息/定时器/任务等
  base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
  
  // 在测试中禁用 RunLoop 的超时监控,防止测试失败因为事件循环超时
  base::test::ScopedDisableRunLoopTimeout disable_timeout;
  
  // “实际业务代码”所在位置,启动事件循环,等待/处理输入/绘制/任务调度等
  run_loop.Run();  // 拉起来我们的事件循环
  
  // 下面是清理工作,和初始化顺序相反地进行资源释放

  // 释放 UI 资源包实例(ResourceBundle)
  ui::ResourceBundle::CleanupSharedInstance();
  
  // 关闭输入法系统
  ui::ShutdownInputMethod();
  
  // 销毁 aura 环境实例
  env.reset(); 
}

​ 上面这个流程非常,非常的长,笔者也是琢磨了一会才整理出来的。我们一个一个看这些内容到底在做什么吧!我们把上面的代码梳理一下,无非就是剩下这些的东西了:

  • 命令行/特性开关(FeatureList)/flags
  • 国际化/ICU
  • 图形/GL/GPU 后端上下文
  • 可丢弃内存 (DiscardableMemory),用于缓存类数据、资源缓存等
  • 任务/消息循环环境(TaskEnvironment, RunLoop,线程池)以支持异步任务/事件/定时器
  • 输入/屏幕/窗口管理器/环境 (aura::Env, Screen, ViewsDelegate etc.)
  • 资源加载 (ResourceBundle),pak 文件/UI 资源等

这些在 Chromium 的测试/example/Views 示例中通常有类似流程。

全局 LazyInstance 和 DiscardableMemoryAllocator

base::LazyInstance<base::TestDiscardableMemoryAllocator>::DestructorAtExit
    g_discardable_memory_allocator = LAZY_INSTANCE_INITIALIZER;
  • 在 Chromium 中,有一个 base::DiscardableMemoryAllocator 接口/类,用于管理可丢弃内存 (discardable memory) 的分配行为。可丢弃内存是指那种可以在系统压力或者某些条件下被释放、回收,但在正常情况下锁定使用的内存区域。源码中有 base/memory/discardable_memory.h 等。 (Chromium)
  • TestDiscardableMemoryAllocator 通常是一个测试专用的实现,在测试/示例/Mock context 中替代生产环境/默认的可丢弃内存分配器,以便测试可以控制行为、统计、清理等。
  • LazyInstance<...>::DestructorAtExit 是一种机制(在 Chromium base 模块中)使得该实例是延迟初始化,在第一次使用时才真正创建;并且在程序/进程退出阶段,被 AtExitManager 注册以确保析构/清理。这样可以解决静态构造顺序问题(static initialization order fiasco)和析构顺序问题。

命令行/FeatureList 初始化

base::CommandLine::Init(argc, argv);
TestTimeouts::Initialize();
...
command_line->AppendSwitch(switches::kDisableDirectComposition);
base::FeatureList::InitInstance(...);
  • 在 Chromium 中,base::CommandLine 是所有子模块查询命令行开关/参数之源头。很多行为(是否启用某 experiment / feature /某些后端 /某些调试模式)都依赖命令行开关。
  • TestTimeouts::Initialize() 是测试框架中设定默认的超时(timeout)常量,以便 TaskEnvironment/RunLoop 等组件能参考这些超时时间。 Chromium 源码里,比如在 base/test/test_timeouts.h 等。
  • 加开关如 kDisableDirectComposition 是为了兼容性:在 Windows 上 DirectComposition 是一个图层与合成 (composition) 技术,用来硬件加速或合成 UI 元素。如果某些组件(例如 InProcessContextFactory)与 DirectComposition 不兼容,或者合成路径存在 Bug,就通过命令行强制禁用。
  • FeatureList::InitInstance(...) 在 Chromium 用来初始化某些 feature flags。源码中 base/feature_list.h/.cc。这使得代码后面的 feature 判断/开关行为能够依据用户/测试/环境设置启用或禁用某些实验性功能。

一次性全局系统初始化

对应代码段:

if (!g_initialized_once) {
    mojo::core::Init();
    gl::init::InitializeGLOneOff(...);
    base::i18n::InitializeICU();
    ui::RegisterPathProvider();
    base::DiscardableMemoryAllocator::SetInstance(...);
    gfx::InitializeFonts();
    g_initialized_once = true;
}

在 Chromium 源码中,这些操作各自对应如下:

操作对应源码 / 模块为什么要做这一步/原因
mojo::core::Init()mojo/core 子模块;IPC / 进程间通信设施。Chromium 是多进程架构(Browser / Renderer / GPU / Utility 等进程)。即使在一个简单的 UI 示例中,某些子系统(比如 GPU 或 Compositor)可能会用 IPC 或内部消息机制,或者依赖 Mojo 的一些基础设施。初始化 Mojo 核心以确保这些能工作。
gl::init::InitializeGLOneOff(...)在 Chromium 的 gpugl 模块,或在 ui/gl 子模块中。初始化 OpenGL 之类图形后端的基础设施/上下文/驱动/GPU 功能检测等。这通常做一次,因为重复初始化会有开销或冲突。
base::i18n::InitializeICU()国际化模块:base/i18n/支持本地化/文字编码/日期/数字格式等。在用户界面中通常很早就要可用。
ui::RegisterPathProvider()ui/base/path_provider 模块注册路径提供者允许查询资源目录、pak 文件路径、图标/图片/语言资源包等。许多子系统要查 UI 测试资源(pak files)、字体文件等资源,需要路径服务来找到它们。
base::DiscardableMemoryAllocator::SetInstance(...)与前面的 LazyInstance 的使用相配合;在 base/memory/discardable_memory_allocator.h.cc 中实现。把当前的(测试版本的)可丢弃内存分配器设为全局实例,以便所有试图使用 discardable memory 的代码都使用这个单例/测试版本,而不是默认版本。便于测试可控性/统计/清理。
gfx::InitializeFonts()gfxskia/字体子系统里加载平台字体、设置字体缓存、准备字体渲染。没有字体系统/字体加载,UI 中的文字、图标、布局等不能正确显示。

这段代码确保这些子系统在真正开始 UI/Views/Compositor/屏幕等之前都已准备好。


任务环境与消息循环

对应:

base::test::TaskEnvironment task_environment(
    base::test::TaskEnvironment::MainThreadType::UI);
...
base::RunLoop run_loop(...);
run_loop.Run();
  • 在 Chromium 的测试框架(例如单元测试/组件测试/视图示例)中,base::test::TaskEnvironment 是很重要的组件。它提供一个“任务环境”,使得:
    1. 能够 post 任务到主线程或线程池;
    2. 能够一个事件循环 (RunLoop) 来消费这些任务/消息/定时器/异步事件;
    3. 在 UI 类型的主线程上支持 UI 消息泵(message pump),即 Windows 的消息/WM 等,或 Mac/Linux 的平台消息/事件/输入/绘制等。 Chromium 的 base/test/task_environment.h 中 MainThreadType: UI 表示你希望事件循环能处理 UI 消息类型。
  • 此外,RunLoop 是 Chromium 中的主事件循环机制,用来等待/分派/处理消息/事件/callback。许多组件(Views/Aura/Compositor/Display/Screen 等)在 UI 测试模式下依赖这个 RunLoop 来模拟真实 UI 线程行为。
  • ScopedDisableRunLoopTimeout/类似机制用来测试中防止测试因为事件循环卡住或超时被框架杀掉或认为失败。

UI 环境/Aura/Views/输入/屏幕等

// 创建 aura 环境
auto env = aura::Env::CreateInstance();
env->set_context_factory(...);
ui::InitializeInputMethodForTesting();
views::DesktopTestViewsDelegate views_delegate;
wm::WMState wm_state;
auto desktop_screen = views::CreateDesktopScreen();

对应 Chromium 源码如下:

  • aura::Env 是 Chromium UI 子系统的环境对象,管理窗口树(window tree)、屏幕 (display) 信息、输入事件分发、焦点、窗口管理等。源自 ui/aura/env.h/.cc
  • context_factory / TestContextFactories 是一组工厂,负责创建合成器 (Compositor)、图形/GL 上下文/绘制上下文等。 在 Views 示例/测试中通常替换成 TestContextFactoriesTestCompositorContextFactory 等,以便渲染可以在测试/离屏/模拟屏幕环境中工作。
  • ui::InitializeInputMethodForTesting() 是在 Chromium 中专门为测试输入法 (IME) 做的初始化,这样文本输入/键盘事件/IME 模拟都能正确工作。对于国际化 unicode 输入、复杂输入法(例如中文/日文/韩语)等尤其重要。
  • views::DesktopTestViewsDelegate:Views 框架中的 delegate,用来处理一些 UI 框架层面的行为,比如对话框/窗口创建方式/平台差异/拖动/关闭事件等。在测试/示例中用测试版本可以避免许多依赖于操作系统/窗口管理器的复杂行为。
  • wm::WMState:window manager state,用在桌面 UI 模块里(例如 Aura + Views)管理窗口管理器是否可见/活动/某些交互规则等。可能在 Linux/Windows/macOS 平台上行为不同。
  • display::Screen / views::CreateDesktopScreen():提供屏幕信息,例如屏幕大小/分辨率/缩放 (DPI)/变换等。UI 布局/渲染/视图会依据这些做正确定位/缩放。

资源包 ResourceBundle

base::FilePath ui_test_pak_path;
PathService::Get(ui::UI_TEST_PAK, &ui_test_pak_path);
ui::ResourceBundle::InitSharedInstanceWithPakPath(ui_test_pak_path);

对应:

  • Chrome 的 UI 资源(字符串、图标、图片、布局 etc.)通常打包在 .pak 文件里。pak 文件由构建系统生成,包含多个 locale 的资源、UI 序列化/图片资源等。源码里有 ui/base/resource/resource_bundle.h/.cc 等。
  • PathService 是 Chromium 中用于注册与查找各种路径(资源目录、数据目录、测试资源目录等)的机制。通过 RegisterPathProvider() 前面已经注册过某些关键路径(例如 UI_TEST_PAK 或 UI resource path 等),这样 PathService::Get(ui::UI_TEST_PAK, ...) 能返回正确的路径给 pak 文件。
  • ResourceBundle::InitSharedInstanceWithPakPath(...) 初始化 ResourceBundle 的全局共享实例 (singleton),以便后续 UI 资源加载(localized strings, images, icons)能从该 pak 文件读取。

清理阶段

run_loop.Run() 返回之后:

ui::ResourceBundle::CleanupSharedInstance();
ui::ShutdownInputMethod();
env.reset();

在 Chromium 源中,有对应的析构/清理机制防止资源泄露/状态残留:

  • ResourceBundle::CleanupSharedInstance() 清理 ResourceBundle,全局资源加载器/缓存/文件映射等释放。
  • ui::ShutdownInputMethod() 反初始化输入法系统。
  • aura::Env 销毁,包括释放屏幕、窗口树、输入事件监听、上下文工厂等资源。
  • LazyInstance + DestructorAtExit 会在进程退出阶段确保分配器被销毁。

架构示意图(ASCII 图)

​ 上面的资源简单的讲就是这样的

 +-------------------------+
 |        main()           |
 |                         |
 |  ┌───────────────────┐  |
 |  | CommandLine &     |<----- 解析开关 / 特性 flags
 |  | FeatureList       |       (如 disable/enable features)
 |  └───────────────────┘  |
 |           |             |
 |  ┌───────────────────┐  |
 |  | One-time Init     |  |  ICY / Mojo / GL / Fonts / PathProvider /
 |  | (ICU / Mojo /     |  |  DiscardableMemoryAllocator 等
 |  |  GL / Fonts /     |  |
 |  |  Path / DMA)      |  |
 |  └───────────────────┘  |
 |           |             |
 |  ┌───────────────────┐  |
 |  | Task Environment   |  | UI 主线程类型任务环境
 |  └───────────────────┘  |
 |           |             |
 |  ┌───────────────────┐  |
 |  | ContextFactory     |  | 创建绘图/合成上下文(Compositor 等)
 |  └───────────────────┘  |
 |           |             |
 |  ┌───────────────────┐  |
 |  | ResourceBundle     |  | 资源(pak)加载:图标/字符串/UI 资源
 |  └───────────────────┘  |
 |           |             |
 |  ┌───────────────────┐  |
 |  | Aura Env + Views   |<-- Window / 输入 /事件分发 /屏幕信息 /Widget etc
 |  └───────────────────┘  |
 |           |             |
 |  ┌───────────────────┐  |
 |  | Screen, WM State,  |  屏幕参数/窗口管理状态/ViewsDelegate
 |  | ViewsDelegate, IME |  
 |  └───────────────────┘  |
 |           |             |
 |  ┌───────────────────┐  |
 |  | RunLoop / Event     |  ← 事件循环 /消息队列 /任务调度
 |  | Loop               |  
 |  └───────────────────┘  |
 |           |             |
 |  └── 清理阶段(Cleanup etc)──┘
 +-------------------------+

​ 我们分析完了,下一步就是我们准备自己写demo了

Reference

​ 笔者把example的部分贴出来,一方面是因为Chrome代码变动比较大,也比较频繁(是的,我看的这几个文件甚至还在活跃的被编辑),为此,需要贴出来最原始的代码给大家参考,方便未来的时候我们进行参考和客制化的开发。

​ 由于笔者化简了example的部分,为此这里需要将原始的代码和链接路径一并给出来方便各位读者快速的参考和翻阅

example_main.cc,这个文件是views_example的入口处,如果您不确定,请在调试的时候在launch.json上书写"stopAtEntry": false,如果你没有launch.json,请您自行参考笔者的博客进行配置:

Chrome源码配置参考,请直奔VSCode配置部分

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/views/examples/examples_main.h"

#include "base/at_exit.h"
#include "base/command_line.h"
#include "base/test/test_timeouts.h"
#include "build/build_config.h"
#include "build/buildflag.h"
#include "ui/views/examples/examples_main_proc.h"

#if BUILDFLAG(IS_MAC)
#include "ui/views/examples/examples_main_mac_support.h"
#endif

#if BUILDFLAG(IS_MAC)
int ViewsExamplesMain(int argc, char** argv) {
#else
int main(int argc, char** argv) {
#endif  // BUILDFLAG(IS_MAC)
  base::CommandLine::Init(argc, argv);

  // The use of base::test::TaskEnvironment in the following function relies on
  // the timeout values from TestTimeouts.
  TestTimeouts::Initialize();

  base::AtExitManager at_exit;

#if BUILDFLAG(IS_MAC)
  UpdateFrameworkBundlePath();
#endif

  return static_cast<int>(views::examples::ExamplesMainProc());
}

上面的是入口文件,下面的部分是真正的启动逻辑

examples_main_proc.cc

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/views/examples/examples_main_proc.h"

#include <memory>
#include <string>
#include <utility>

#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/i18n/icu_util.h"
#include "base/lazy_instance.h"
#include "base/memory/ptr_util.h"
#include "base/path_service.h"
#include "base/power_monitor/power_monitor.h"
#include "base/power_monitor/power_monitor_device_source.h"
#include "base/run_loop.h"
#include "base/test/scoped_run_loop_timeout.h"
#include "base/test/task_environment.h"
#include "base/test/test_discardable_memory_allocator.h"
#include "base/test/test_timeouts.h"
#include "build/build_config.h"
#include "components/viz/host/host_frame_sink_manager.h"
#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
#include "mojo/core/embedder/embedder.h"
#include "ui/accessibility/platform/ax_platform_for_test.h"
#include "ui/base/ime/init/input_method_initializer.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/ui_base_paths.h"
#include "ui/color/color_provider_manager.h"
#include "ui/compositor/compositor_switches.h"
#include "ui/compositor/test/in_process_context_factory.h"
#include "ui/compositor/test/test_context_factories.h"
#include "ui/display/screen.h"
#include "ui/gfx/font_util.h"
#include "ui/gfx/image/image.h"
#include "ui/gl/gl_utils.h"
#include "ui/gl/init/gl_factory.h"
#include "ui/views/buildflags.h"
#include "ui/views/examples/example_base.h"
#include "ui/views/examples/examples_color_mixer.h"
#include "ui/views/examples/examples_window.h"
#include "ui/views/test/desktop_test_views_delegate.h"
#include "ui/views/widget/any_widget_observer.h"
#include "ui/views/widget/widget.h"

#if defined(USE_AURA)
#include "ui/aura/env.h"
#include "ui/wm/core/wm_state.h"
#endif

#if BUILDFLAG(IS_CHROMEOS)
#include "ui/views/examples/examples_views_delegate_chromeos.h"
#endif

#if BUILDFLAG(ENABLE_DESKTOP_AURA)
#include "ui/views/widget/desktop_aura/desktop_screen.h"
#endif

#if BUILDFLAG(IS_WIN)
#include "ui/base/win/scoped_ole_initializer.h"
#include "ui/views/examples/examples_skia_gold_pixel_diff.h"
#endif

#if BUILDFLAG(IS_MAC)
#include "ui/views/examples/examples_main_proc_mac_parts.h"
#endif

#if BUILDFLAG(IS_OZONE)
#include "ui/ozone/public/ozone_platform.h"
#endif

namespace views::examples {

base::LazyInstance<base::TestDiscardableMemoryAllocator>::DestructorAtExit
    g_discardable_memory_allocator = LAZY_INSTANCE_INITIALIZER;

bool g_initialized_once = false;

ExamplesExitCode ExamplesMainProc(bool under_test, ExampleVector examples) {
#if BUILDFLAG(IS_WIN)
  ui::ScopedOleInitializer ole_initializer;
#endif

#if BUILDFLAG(IS_MAC)
  ExamplesMainProcMacParts();
#endif

  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();

  if (CheckCommandLineUsage()) {
    return ExamplesExitCode::kSucceeded;
  }

  ui::AXPlatformForTest ax_platform;

  // Disabling Direct Composition works around the limitation that
  // InProcessContextFactory doesn't work with Direct Composition, causing the
  // window to not render. See http://crbug.com/936249.
  command_line->AppendSwitch(switches::kDisableDirectComposition);

  base::FeatureList::InitInstance(
      command_line->GetSwitchValueASCII(switches::kEnableFeatures),
      command_line->GetSwitchValueASCII(switches::kDisableFeatures));

  if (under_test) {
    command_line->AppendSwitch(switches::kEnablePixelOutputInTests);
  }

#if BUILDFLAG(IS_OZONE)
  ui::OzonePlatform::InitParams params;
  params.single_process = true;
  ui::OzonePlatform::InitializeForGPU(params);
#endif

  // ExamplesMainProc can be called multiple times in a test suite.
  // These methods should only be initialized once.
  if (!g_initialized_once) {
    mojo::core::Init();

    gl::init::InitializeGLOneOff(
        /*gpu_preference=*/gl::GpuPreference::kDefault);

    base::i18n::InitializeICU();

    ui::RegisterPathProvider();

    base::DiscardableMemoryAllocator::SetInstance(
        g_discardable_memory_allocator.Pointer());

    gfx::InitializeFonts();

    g_initialized_once = true;
  }

  // Viz depends on the task environment to correctly tear down.
  base::test::TaskEnvironment task_environment(
      base::test::TaskEnvironment::MainThreadType::UI);

  // The ContextFactory must exist before any Compositors are created.
  auto context_factories =
      std::make_unique<ui::TestContextFactories>(under_test,
                                                 /*output_to_window=*/true);

  base::FilePath ui_test_pak_path;
  CHECK(base::PathService::Get(ui::UI_TEST_PAK, &ui_test_pak_path));
  ui::ResourceBundle::InitSharedInstanceWithPakPath(ui_test_pak_path);

  base::FilePath views_examples_resources_pak_path;
  CHECK(base::PathService::Get(base::DIR_ASSETS,
                               &views_examples_resources_pak_path));
  ui::ResourceBundle::GetSharedInstance().AddDataPackFromPath(
      views_examples_resources_pak_path.AppendASCII(
          "views_examples_resources.pak"),
      ui::k100Percent);

  ui::ColorProviderManager::Get().AppendColorProviderInitializer(
      base::BindRepeating(&AddExamplesColorMixers));

#if defined(USE_AURA)
  std::unique_ptr<aura::Env> env = aura::Env::CreateInstance();
  aura::Env::GetInstance()->set_context_factory(
      context_factories->GetContextFactory());
#endif
  ui::InitializeInputMethodForTesting();

  ExamplesExitCode compare_result = ExamplesExitCode::kSucceeded;

  {
#if BUILDFLAG(IS_CHROMEOS)
    ExamplesViewsDelegateChromeOS views_delegate;
#else  // BUILDFLAG(IS_CHROMEOS)
    views::DesktopTestViewsDelegate views_delegate;
#if BUILDFLAG(IS_MAC)
    views_delegate.set_context_factory(context_factories->GetContextFactory());
#endif
#if defined(USE_AURA)
    wm::WMState wm_state;
#endif
#endif  // !BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(IS_MAC)
    display::ScopedNativeScreen desktop_screen;
#elif BUILDFLAG(ENABLE_DESKTOP_AURA)
    std::unique_ptr<display::Screen> desktop_screen =
        views::CreateDesktopScreen();
#endif

    base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);

#if BUILDFLAG(IS_WIN)
    ExamplesSkiaGoldPixelDiff pixel_diff;
    views::AnyWidgetObserver widget_observer{
        views::test::AnyWidgetTestPasskey()};

    // If this app isn't a test, it shouldn't timeout.
    auto disable_timeout =
        std::make_unique<base::test::ScopedDisableRunLoopTimeout>();

    if (under_test) {
      pixel_diff.Init("ViewsExamples");
      widget_observer.set_shown_callback(
          base::BindRepeating(&ExamplesSkiaGoldPixelDiff::OnExamplesWindowShown,
                              base::Unretained(&pixel_diff)));
      // Enable the timeout since we're not running in a test.
      disable_timeout.reset();
    }
#else
    base::test::ScopedDisableRunLoopTimeout disable_timeout;
#endif

    if (examples.empty()) {
      views::examples::ShowExamplesWindow(run_loop.QuitClosure());
    } else {
      views::examples::ShowExamplesWindow(run_loop.QuitClosure(),
                                          std::move(examples));
    }

    run_loop.Run();

#if BUILDFLAG(IS_WIN)
    compare_result = pixel_diff.get_result();
#endif

    ui::ResourceBundle::CleanupSharedInstance();
  }

  ui::ShutdownInputMethod();

#if defined(USE_AURA)
  env.reset();
#endif

  return compare_result;
}

}  // namespace views::examples