美文网首页
CEF入门必看!CEF官方教程(Wiki)翻译&校对&注语

CEF入门必看!CEF官方教程(Wiki)翻译&校对&注语

作者: 饮茶先啦靓仔 | 来源:发表于2023-01-29 22:51 被阅读0次

翻译自CEF官方英文教程,若有疏忽欢迎指正。欢迎评论区讨论~

Chromium Embedded Framework

介绍

Chromium Embedded Framework (CEF) 是一个基于Google Chromium的开源项目。与主要专注于 Google Chrome 应用程序开发的 Chromium 项目本身不同,CEF 专注于促进第三方应用程序中的嵌入式浏览器用例。CEF 通过提供稳定的 API、发布特定 Chromium 版本的代码分支和二进制发行版,将用户与底层 Chromium 和 Blink 代码的复杂性隔离开来。CEF 中的大多数功能都有默认实现,这些实现提供丰富的功能,只需要用户进行很少的集成工作。截至本文发表时,全球范围内已安装超过 1 亿个 CEF 实例,这些实例嵌入到来自众多公司和行业的产品中。使用 CEF 的公司和产品的部分列表可在CEF 维基百科页面上找到。CEF 的一些产品案例包括:

  • 在现有本机应用程序中嵌入符合 HTML5 的 Web 浏览器控件。
  • 创建一个轻量级本机“外壳”应用程序,该应用程序托管主要使用 Web 技术开发的用户界面。
  • 在具有自己的自定义绘图框架的应用程序中“离屏”呈现 Web 内容。
  • 充当对现有 Web 属性和应用程序进行自动测试的主机。

CEF3 是基于多进程Chromium Content API的下一代 CEF 。CEF3 的多进程架构的优势包括:

  • 改进的性能和稳定性(JavaScript 和插件在单独的进程中运行)。
  • 支持 Retina 显示屏。
  • WebGL 和 3D CSS 的 GPU 加速。
  • 很酷的新功能,如 WebRTC(网络摄像头支持)和语音输入。
  • 通过 DevTools 远程调试协议和ChromeDriver2更好地自动化 UI 测试。
  • 更快地使用当前和未来的 Web 功能和标准。

本文档介绍了使用 CEF3 开发应用程序时涉及的一般概念。

入门

使用二进制发行版(二进制库)

CEF3 的二进制库可从项目下载页面获得。它们包含在特定平台(Windows、MacOS 或 Linux)上构建特定版本的 CEF3 所需的所有文件。有关如何使用 CEF3 二进制发行版创建简单应用程序的详细说明,请参阅Tutorial Wiki 页面。

无论平台如何,所有二进制发行版都共享相同的通用结构:

  • CMakeLists.txt提供CMake 配置,用于构建包含在二进制发行版中的测试应用程序。特定于平台的构建说明在此文件顶部作为注释提供。
  • Debug包含调试构建 CEF 共享库 (libcef) 和在平台上运行所需的任何其他库。
  • include包含所有必需的 CEF 头文件。
  • libcef_dll包含所有使用 CEF C++ API 的应用程序都必须链接的 libcef_dll_wrapper 静态库的源代码。有关详细信息,请参阅“C++ Wrapper ”(C++包装器)部分。
  • Release包含 CEF 共享库 (libcef) 的发布构建以及在平台上运行所需的任何其他库。
  • Resources包含使用 CEF 的应用程序所需的资源(仅限 Windows 和 Linux)。这包括 .pak 文件(具有全局资源的二进制文件)和可能的其他文件,例如取决于平台。
  • tests/cefclient包含配置为使用二进制发行版中的文件构建的 cefclient 示例应用程序。此应用程序演示了广泛的 CEF 功能。
  • tests/cefsimple包含配置为使用二进制发行版中的文件构建的 cefsimple 示例应用程序。此应用程序演示了创建浏览器窗口所需的最少功能。
  • tests/ceftests包含配置为使用二进制发行版中的文件构建的 ceftests 示例应用程序。此应用程序为 CEF API 和功能提供单元测试覆盖率。

每个二进制发行版还包含一个 README.txt 文件,该文件更详细地描述了特定于平台的发行版,以及一个包含 CEF 的 BSD 许可证的 LICENSE.txt 文件。在发布基于 CEF 的应用程序时,您应该在应用程序分发的某处包含许可文本。例如,您可以将其列在应用程序 UI 的“关于”或“致谢名单”页面上,或者列在与应用程序捆绑在一起的文档中。通过分别加载“about:license”和“about:credits”,还可以在 CEF3 浏览器窗口中获取许可证和信用信息。

可以使用标准平台构建工具构建基于 CEF 二进制发行版的应用程序。这包括 Windows 上的 Visual Studio、MacOS 上的 Xcode 和 Linux 上的 gcc/make。项目下载页面包含有关特定二进制版本所需的操作系统和构建工具版本的信息。在 Linux 上构建时,还要特别注意列出的包依赖项。

使用源代码构建

CEF 可以在本地从源代码构建,也可以使用自动构建系统(如TeamCity )构建。这需要通过 Git 下载 Chromium 和 CEF 源代码。Chromium 代码库非常大,仅建议在 RAM 超过 8GB 的机器上从源代码构建 Chromium。BranchesAndBuilding Wiki 页面上提供了从源代码构建 Chromium 和 CEF 的详细说明。

示例应用程序

cefclient 示例应用程序是 CEF 集成的完整工作示例,并以源代码形式包含在每个二进制发行版中。使用 CEF 创建新应用程序的最简单方法是从 cefclient 应用程序开始并删除不需要的部分。本文档中的许多示例都来自 cefclient 应用程序。

重要概念

开发基于 CEF3 的应用程序有一些重要的基本概念,在继续之前应该了解这些概念。

C++ 包装器

libcef 共享库导出一个 C API,将用户与 CEF 运行时和代码库隔离开来。作为二进制版本的一部分以源代码形式分发的 libcef_dll_wrapper 项目将这个导出的 C API 包装在 C++ API 中,然后链接到客户端应用程序。此 C/C++ API 转换层的代码由转换工具自动生成。UsingTheCAPI页面上描述了 C API 的直接使用。

进程

CEF3 使用多个进程运行。处理窗口创建、UI 和网络访问的主要进程称为“浏览器”进程。这通常是与主应用程序相同的进程,并且大部分应用程序逻辑将在浏览器进程中运行。Blink 渲染和 JavaScript 执行发生在单独的“渲染”进程中。一些应用程序逻辑,例如 JavaScript 绑定和 DOM 访问,也会在渲染进程中运行。默认进程模型将为每个唯一的来源(方案+域,即URL)生成一个新的渲染进程。其他进程将根据需要产生,例如处理加速合成的“gpu”进程。

默认情况下,主应用程序可执行文件将被多次启动为系统中的独立进程。这是通过传递到 CefExecuteProcess 函数的命令行标志来处理的。如果主应用程序可执行文件很大,加载时间很长,或者不适用于非浏览器进程,可以使用单独的可执行文件。这可以通过 CefSettings.browser_subprocess_path 变量进行配置。有关详细信息,请参阅“应用程序结构”部分。

CEF3 生成的独立进程使用进程间通信 (IPC) 进行通信。在浏览器和渲染进程中实现的应用程序逻辑可以通过来回发送异步消息进行通信。渲染进程中的JavaScriptIntegration可以调用位于浏览器进程中的异步 API。有关详细信息,请参阅“进程间通信”部分。

针对特定平台的调试提示也适用于WindowsMacOSLinux

线程

CEF3 中的每个进程都运行多个线程。有关线程的完整列表,请参阅cef_thread_id_t枚举。这些是一些常用的线程:

  • TID_UI线程是浏览器进程中的主线程。如果使用 CefSettings.multi_threaded_message_loop 值为 false 调用 CefInitialize(),则此线程将与主应用程序线程相同。
  • TID_IO线程用于浏览器进程处理IPC和网络消息。
  • TID_FILE_*线程用于浏览器进程与文件系统交互。阻塞操作只能在该线程或客户端应用程序创建的CefThread上执行。
  • TID_RENDERER线程是渲染器进程中的主线程。所有 Blink 和 V8 交互都必须在此线程上进行。

由于 CEF 的多线程特性,使用消息传递或锁定来保护数据成员免受多线程访问非常重要。CefPostTask 系列函数支持在线程之间轻松传递异步消息。有关详细信息,请参阅“发布任务”部分。

可以使用 CefCurrentlyOn() 函数验证当前所在线程。CEF 示例应用程序使用以下定义来验证当前是否在预期线程上执行。这些定义包含在include/wrapper/cef_helpers.h头文件中。

#define CEF_REQUIRE_UI_THREAD() DCHECK(CefCurrentlyOn(TID_UI));
#define CEF_REQUIRE_IO_THREAD() DCHECK(CefCurrentlyOn(TID_IO));
#define CEF_REQUIRE_FILE_BACKGROUND_THREAD() \
 DCHECK(CefCurrentlyOn(TID_FILE_BACKGROUND));
#define CEF_REQUIRE_FILE_USER_VISIBLE_THREAD() \
 DCHECK(CefCurrentlyOn(TID_FILE_USER_VISIBLE));
#define CEF_REQUIRE_FILE_USER_BLOCKING_THREAD() \
 DCHECK(CefCurrentlyOn(TID_FILE_USER_BLOCKING));
#define CEF_REQUIRE_RENDERER_THREAD() DCHECK(CefCurrentlyOn(TID_RENDERER));

为了支持对代码块的同步访问,CEF 通过include/base/cef_lock.h头文件提供了 base::Lock 和 base::AutoLock 类型。例如:

译者注语:如果编译器支持C++11及以上标准,可以使用std::mutex、std::lock_guard等类,效果类似。

// Include the necessary header.
#include "include/base/cef_lock.h"

// Class declaration.
class MyClass : public CefBaseRefCounted {
 public:
  MyClass() : value_(0) {}
  // Method that may be called on multiple threads.
  void IncrementValue();
 private:
  // Value that may be accessed on multiple theads.
  int value_;
  // Lock used to protect access to |value_|.
  base::Lock lock_;
  IMPLEMENT_REFCOUNTING(MyClass);
};

// Class implementation.
void MyClass::IncrementValue() {
  // Acquire the lock for the scope of this method.
  base::AutoLock lock_scope(lock_);
  // |value_| can now be modified safely.
  value_++;
}

引用计数

所有框架类都实现了 CefBase[RefCounted|Scoped] 接口,所有实例指针都使用 CefRefPtr 智能指针实现进行处理,该实现通过调用 AddRef() 和 Release() 自动处理引用计数。实现这些类的最简单方法如下:

class MyClass : public CefBaseRefCounted {
 public:
  // Various class methods here...

 private:
  // Various class members here...

  IMPLEMENT_REFCOUNTING(MyClass);  // Provides atomic refcounting implementation.
};

// References a MyClass instance
CefRefPtr<MyClass> my_class = new MyClass();

字符串

CEF 定义了自己的数据结构来表示字符串。原因在于:

  • libcef 库和主应用程序可能使用不同的运行时来管理堆内存。所有对象,包括字符串,都需要使用分配内存的相同运行时来释放。
  • 可以编译 libcef 库以支持不同的底层字符串类型(UTF8、UTF16 或宽)。默认值为 UTF16,但可以通过修改cef_string.h中的定义并重新编译 CEF 来更改。选择宽字符串类型时请记住,大小会因平台而异。

对于 UTF16,字符串结构如下所示:

typedef struct _cef_string_utf16_t {
  char16* str;  // Pointer to the string
  size_t length;  // String length
  void (*dtor)(char16* str);  // Destructor for freeing the string on the correct heap
} cef_string_utf16_t;

然后将选定的字符串类型定义为通用类型:

typedef char16 cef_char_t;
typedef cef_string_utf16_t cef_string_t;

CEF 提供了许多 C API 函数来操作 CEF 字符串类型(通过#defines 映射到特定于类型的函数)。例如:

  • cef_string_set将在复制或不复制值的情况下将字符串值分配给结构。
  • cef_string_clear将清除字符串值。
  • cef_string_cmp将比较两个字符串值。

CEF 还提供了在所有支持的字符串类型(ASCII、UTF8、UTF16 和宽)之间进行转换的函数。有关完整的函数列表,请参阅cef_string.hcef_string_types.h标头。

CefString 类简化了 C++ 中 CEF 字符串的使用。CefString 提供与 std::string (UTF8) 和 std::wstring (wide) 类型之间的自动转换。它还可以用于包装现有的 cef_string_t 结构以用于分配目的。

对 std::string 的赋值:

std::string str = “Some UTF8 string”;

// Equivalent ways of assigning |str| to |cef_str|. Conversion from UTF8 will occur if necessary.
CefString cef_str(str);
cef_str = str;
cef_str.FromString(str);

// Equivalent ways of assigning |cef_str| to |str|. Conversion to UTF8 will occur if necessary.
str = cef_str;
str = cef_str.ToString();

对 std::wstring 的赋值:

std::wstring str = “Some wide string”;

// Equivalent ways of assigning |str| to |cef_str|. Conversion from wide will occur if necessary.
CefString cef_str(str);
cef_str = str;
cef_str.FromWString(str);

// Equivalent ways of assigning |cef_str| to |str|. Conversion to wide will occur if necessary.
str = cef_str;
str = cef_str.ToWString();

如果您知道格式是 ASCII,请使用 FromASCII() 方法:

const char* cstr = “Some ASCII string”;
CefString cef_str;
cef_str.FromASCII(cstr);

一些结构如 CefSettings 有 cef_string_t 成员。CefString 可用于简化对这些成员的分配:

译者注语:这里是相当于将settings.log_file暂时交给一个匿名的CefString控制,这个匿名的CefString对象通过调用FromASCII成员函数,间接地修改了settings.log_file。

CefSettings settings;
const char* path = “/path/to/log.txt”;

// Equivalent assignments.
CefString(&settings.log_file).FromASCII(path);
cef_string_from_ascii(path, strlen(path), &settings.log_file);

命令行参数

CEF3 和 Chromium 中的许多功能都可以使用命令行参数进行配置。这些参数采用“--some-argument[=optional-param]”的形式,并通过 CefExecuteProcess() 和 CefMainArgs 结构(参见文章后面的“应用程序结构”部分)传递到 CEF。

  • 若需要禁用命令行参数的处理,请在将 CefSettings 结构传递到 CefInitialize() 之前,将CefSettings.command_line_args_disabled 设置为 true。
  • 若需要在主应用程序中指定 CEF/Chromium 命令行参数,请调用 CefApp::OnBeforeCommandLineProcessing() 方法。
  • 若需要将特定于应用程序(非 CEF/Chromium)的命令行参数传递给子进程,请调用 CefBrowserProcessHandler::OnBeforeChildProcessLaunch() 方法。

关于 CEF/Chromium 命令行开关的更多信息,请参阅shared/common/client_switches.cc中的注释。

应用布局

应用程序布局可能因平台而异。例如,在 MacOS 上,您的应用程序布局必须遵循特定的应用程序打包结构。Windows 和 Linux 更加灵活,允许您自定义存储 CEF 库和资源的位置。对于所需布局的完整工作示例,您可以从项目下载页面下载“示例应用程序” 。一些发布的文件是必需的,一些是可选的。每个文件的要求和附加信息可以在二进制发行版的README.txt 文件中找到。

Windows

在 Windows 上,默认布局将 libcef 库和相关资源放置在应用程序可执行文件旁边。4692 分支的目录结构如下所示:

Application/
    cefclient.exe  <= cefclient application executable
    libcef.dll <= main CEF library
    icudtl.dat <= unicode support data
    libEGL.dll, libGLESv2.dll, ... <= accelerated compositing support libraries
    chrome_100_percent.pak, chrome_200_percent.pak, resources.pak <= non-localized resources and strings
    snapshot_blob.bin, v8_context_snapshot.bin <= V8 initial snapshot
    locales/
        en-US.pak, ... <= locale-specific resources and strings

可以使用 CefSettings 结构自定义 CEF 库和资源文件的位置(有关详细信息,请参阅 README.txt 文件或“CefSettings”部分)。Windows 上的 cefclient 应用程序通过 cefclient/resources/win/cefclient.rc中的 BINARY 资源类型编译资源,但应用程序可以轻松地从本地文件系统加载资源。

Linux

在 Linux 上,默认布局将 libcef 库和相关资源放在应用程序可执行文件旁边。但是请注意,libcef.so 在客户端发行版中的位置与您自己构建的二进制发行版中的位置存在差异。该位置取决于在构建应用程序可执行文件时如何设置链接器 rpath 值。例如,值“-Wl,-rpath,”。(“.”表示当前目录)将允许您将 libcef.so 放在应用程序可执行文件旁边。也可以使用 LD_LIBRARY_PATH 环境变量指定 libcef.so 的路径。4692 分支的目录结构如下所示:

Application/
    cefclient  <= cefclient application executable
    chrome-sandbox <= sandbox support binary
    libcef.so <= main CEF library
    icudtl.dat <= unicode support data
    chrome_100_percent.pak, chrome_200_percent.pak, resources.pak <= non-localized resources and strings
    snapshot_blob.bin, v8_context_snapshot.bin <= V8 initial snapshot
    locales/
        en-US.pak, ... <= locale-specific resources and strings
    files/
        binding.html, ... <= cefclient application resources

可以使用 CefSettings 结构自定义 CEF 库和资源文件的位置(有关详细信息,请参阅“CefSettings”部分的 README.txt 文件)。

MacOS

MacOS 上的应用程序(应用程序包)布局由 Chromium 实现强制执行,因此不是很灵活。4692 分支的目录结构如下所示:

cefclient.app/
    Contents/
        Frameworks/
            Chromium Embedded Framework.framework/
                Chromium Embedded Framework <= main application library
                Resources/
                    chrome_100_percent.pak, chrome_200_percent.pak, resources.pak, ... <= non-localized resources and strings
                    icudtl.dat <= unicode support data
                    snapshot_blob.bin, v8_context_snapshot.bin <= V8 initial snapshot
                    en.lproj/, ... <= locale-specific resources and strings
            cefclient Helper.app/
                Contents/
                    Info.plist
                    MacOS/
                        cefclient Helper <= helper executable
                    Pkginfo
        Info.plist
        MacOS/
            cefclient <= cefclient application executable
        Pkginfo
        Resources/
            binding.html, ... <= cefclient application resources

“Chromium Embedded Framework.framework”是一个未版本控制的框架,其中包含所有 CEF 二进制文件和资源。可执行文件(cefclient、cefclient Helper 等)按此处所述动态加载 CEF 框架。

“cefclient Helper”应用程序用于执行具有不同特征的单独进程(渲染器、插件等)。它需要有一个单独的应用程序包和 Info.plist 文件,以便除其他外,它不显示停靠栏图标。

应用结构

每个 CEF3 应用程序都具有相同的通用结构。

  • 提供初始化 CEF 并运行子进程可执行逻辑或 CEF 消息循环的入口点函数。
  • 提供CefApp的实现来处理特定于进程的回调。
  • 提供CefClient的实现来处理特定于浏览器实例的回调。
  • 调用 CefBrowserHost::CreateBrowser() 创建浏览器实例并使用 CefLifeSpanHandler管理浏览器生命周期。

入口函数

如“进程”部分所述,CEF3 应用程序将运行多个进程。这些进程可以全部使用相同的可执行文件,或者可以为子进程指定一个单独的可执行文件。进程的执行从入口函数开始。cefclient/cefclient_win.cccefclient/cefclient_gtk.cccefclient/cefclient_mac.mm分别提供了适用于 Windows、Linux 和 MacOS 的完整平台特定示例。

当启动子进程时,CEF 将使用命令行指定配置信息,这些信息必须通过 CefMainArgs 结构传递到 CefExecuteProcess 函数中。CefMainArgs 的定义是特定于平台的。在 Linux 和 MacOS 上,它接受传递给main() 函数的 argc 和 argv 值。

CefMainArgs main_args(argc, argv);

在 Windows 上,它接受传递到wWinMain() 函数中的实例句柄 (HINSTANCE) 。实例句柄也可以通过 GetModuleHandle(nullptr) 检索。

CefMainArgs main_args(hInstance);

单个可执行文件

当作为单个可执行文件运行时,需要入口函数来区分不同的进程类型。Windows 和 Linux 支持单一可执行文件结构,但 MacOS 不支持。

// Program entry-point function.
int main(int argc, char* argv[]) {
  // Structure for passing command-line arguments.
  // The definition of this structure is platform-specific.
  CefMainArgs main_args(argc, argv);

  // Implementation of the CefApp interface.
  CefRefPtr<MyApp> app(new MyApp);

  // Execute the sub-process logic, if any. This will either return immediately for the browser
  // process or block until the sub-process should exit.
  int exit_code = CefExecuteProcess(main_args, app.get());
  if (exit_code >= 0) {
    // The sub-process terminated, exit now.
    return exit_code;
  }

  // Populate this structure to customize CEF behavior.
  CefSettings settings;

  // Initialize CEF in the main process.
  CefInitialize(main_args, settings, app.get());

  // Run the CEF message loop. This will block until CefQuitMessageLoop() is called.
  CefRunMessageLoop();

  // Shut down CEF.
  CefShutdown();

  return 0;
}

单独的子进程可执行文件

使用单独的子进程可执行文件时,您需要两个单独的可执行项目和两个单独的入口函数。

主进程应用入口函数:

// Program entry-point function.
int main(int argc, char* argv[]) {
  // Load the CEF framework library at runtime instead of linking directly
  // as required by the macOS sandbox implementation.
  CefScopedLibraryLoader library_loader;
  if (!library_loader.LoadInMain())
    return 1;

  // Structure for passing command-line arguments.
  // The definition of this structure is platform-specific.
  CefMainArgs main_args(argc, argv);

  // Implementation of the CefApp interface.
  CefRefPtr<MyApp> app(new MyApp);

  // Populate this structure to customize CEF behavior.
  CefSettings settings;

  // Specify the path for the sub-process executable.
  CefString(&settings.browser_subprocess_path).FromASCII(“/path/to/subprocess”);

  // Initialize CEF in the main process.
  CefInitialize(main_args, settings, app.get());

  // Run the CEF message loop. This will block until CefQuitMessageLoop() is called.
  CefRunMessageLoop();

  // Shut down CEF.
  CefShutdown();

  return 0;
}

子进程应用入口函数:

// Program entry-point function.
int main(int argc, char* argv[]) {
  // Initialize the macOS sandbox for this helper process.
  CefScopedSandboxContext sandbox_context;
  if (!sandbox_context.Initialize(argc, argv))
    return 1;

  // Load the CEF framework library at runtime instead of linking directly
  // as required by the macOS sandbox implementation.
  CefScopedLibraryLoader library_loader;
  if (!library_loader.LoadInHelper())
    return 1;

  // Structure for passing command-line arguments.
  // The definition of this structure is platform-specific.
  CefMainArgs main_args(argc, argv);

  // Implementation of the CefApp interface.
  CefRefPtr<MyApp> app(new MyApp);

  // Execute the sub-process logic. This will block until the sub-process should exit.
  return CefExecuteProcess(main_args, app.get());
}

消息循环集成

CEF 还可以与现有的应用程序消息循环集成,而不是运行自己的消息循环。有两种方法可以做到这一点。

  1. 定期调用 CefDoMessageLoopWork() 而不是调用 CefRunMessageLoop()。每次调用 CefDoMessageLoopWork() 都会执行一次 CEF 消息循环迭代。应谨慎使用此方法。过于频繁地调用该方法会使 CEF 消息循环耗尽并对浏览器性能产生负面影响。过于频繁地调用该方法会对 CPU 使用率产生负面影响。有关高级用法的详细信息,请参阅CefBrowserProcessHandler::OnScheduleMessagePumpWork。您可以在 cefclient 中通过使用“--external-message-pump”命令行标志运行来测试此模式。
  2. 设置 CefSettings.multi_threaded_message_loop = true(仅限 Windows 和 Linux)。这将导致 CEF 在与主应用程序线程不同的线程上运行浏览器 UI 线程。使用这种方法,既不需要调用 CefDoMessageLoopWork() 也不需要调用 CefRunMessageLoop()。CefInitialize() 和 CefShutdown() 仍应在主应用程序线程上调用。您将需要提供自己的机制来与主应用程序线程通信(例如,请参阅 cefclient_win.cpp 中的消息窗口用法)。您可以在 Windows 或 Linux 上的 cefclient 中通过使用“--multi-threaded-message-loop”命令行标志运行来测试此模式。

数据结构CefSettings

CefSettings 结构允许配置应用程序范围的 CEF 设置。一些常用的用以配置的成员变量包括:

  • browser_subprocess_path将为子进程启动的单独可执行文件的路径。有关详细信息,请参阅“单独的子流程可执行文件”部分。
  • multi_threaded_message_loop设置为 true 让浏览器处理消息循环在单独的线程中运行。有关详细信息,请参阅“消息循环集成”部分。
  • command_line_args_disabled设置为 true 以禁用使用标准 CEF 和 Chromium 命令行参数配置浏览器进程功能。有关详细信息,请参阅“命令行参数”部分。
  • cache_path缓存数据将存储在磁盘上的位置。如果为空,内存缓存将用于某些功能,而临时磁盘缓存将用于其他功能。如果指定了缓存路径,HTML5 数据库(如 localStorage)将仅在会话中持久存在。
  • locale将传递给 Blink 的语言环境字符串。如果为空,将使用默认区域设置“en-US”。在 Linux 上忽略此值,其中区域设置是使用具有优先顺序的环境变量解析确定的:LANGUAGE、LC_ALL、LC_MESSAGES 和 LANG。也可以使用“lang”命令行开关进行配置。
  • log_file用于调试日志的目录和文件名。如果为空,将使用默认名称“debug.log”并将文件写入应用程序目录。也可使用“日志文件”命令行开关进行配置。
  • log_severity日志严重性。只会记录此严重级别或更高级别的消息。也可以使用值为“verbose”、“info”、“warning”、“error”、“error-report”或“disable”的“log-severity”命令行开关进行配置。
  • resources_dir_path资源目录的完全限定路径。如果此值为空,则 cef.pak 和/或 devtools_resources.pak 文件必须位于 Windows/Linux 上的模块目录或 MacOS 上的应用程序包资源目录中。也可以使用“resources-dir-path”命令行开关进行配置。
  • locales_dir_path locales 目录的完全限定路径。如果此值为空,则 locales 目录必须位于模块目录中。在始终从应用程序包资源目录加载包文件的 MacOS 上忽略此值。也可使用“locales-dir-path”命令行开关进行配置。
  • remote_debugging_port设置为 1024 到 65535 之间的值以在指定端口上启用远程调试。例如,如果指定了 8080,则远程调试 URL 将为 http://localhost:8080。可以从任何 CEF 或 Chrome 浏览器窗口远程调试 CEF。也可使用“remote-debugging-port”命令行开关进行配置。

CefBrowser 和 CefFrame

CefBrowser和CefFrame对象用于向浏览器发送命令和在回调方法中检索状态信息每个 CefBrowser 对象都有一个代表顶级框架的主 CefFrame 对象和代表子框架的零个或多个 CefFrame 对象。例如,加载两个 iframe 的浏览器将具有三个 CefFrame 对象(顶级框架和两个 iframe)。

在浏览器主框架中加载 URL:

browser->GetMainFrame()->LoadURL(some_url);

向后导航浏览器:

browser->GoBack();

要检索主框架 HTML 内容:

// Implementation of the CefStringVisitor interface.
class Visitor : public CefStringVisitor {
 public:
  Visitor() {}

  // Called asynchronously when the HTML contents are available.
  virtual void Visit(const CefString& string) override {
    // Do something with |string|...
  }

  IMPLEMENT_REFCOUNTING(Visitor);
};

browser->GetMainFrame()->GetSource(new Visitor());

CefBrowser 和 CefFrame 对象同时存在于浏览器进程和渲染进程中。可以通过 CefBrowser::GetHost() 方法在浏览器进程中控制主机行为。例如,可以按如下方式检索窗口浏览器的本地句柄:

// CefWindowHandle is defined as HWND on Windows, NSView* on MacOS
// and (usually) X11 Window on Linux.
CefWindowHandle window_handle = browser->GetHost()->GetWindowHandle();

其他方法可用于历史导航、加载字符串和请求、发送编辑命令、检索文本/html 内容等。相关方法的完整列表,请参阅类文档。

CefApp

CefApp接口提供对进程特定回调的访问。重要的回调包括:

  • OnBeforeCommandLineProcessing提供了以编程方式设置命令行参数的机会。有关详细信息,请参阅“命令行参数”部分。
  • OnRegisterCustomSchemes提供注册自定义方案的机会。有关详细信息,请参阅“请求处理”部分。
  • GetBrowserProcessHandler返回特定于浏览器进程的功能的句柄,包括 OnContextInitialized() 方法。
  • GetRenderProcessHandler返回特定于渲染进程的功能的句柄。这包括与 JavaScript 相关的回调和处理消息。有关详细信息,请参阅JavaScriptIntegration Wiki 页面和“进程间通信”部分。

可以在cefsimple/simple_app.hcefsimple/simple_app.cc中看到一个 CefApp 实现例程。

CefClient

CefClient接口提供对浏览器实例特定回调的访问。单个 CefClient 实例可以在任意数量的浏览器之间共享。重要的回调包括:

  • 处理浏览器生命周期、上下文菜单、对话框、显示通知、拖动事件、焦点事件、键盘事件等。大多数事件处理器(Handlers)都是可选的。请参阅类文档了解不实现特定事件处理器的副作用。
  • OnProcessMessageReceived当从呈现进程接收到 IPC 消息时调用。有关详细信息,请参阅“进程间通信”部分。

CefClient 实现例程可以在cefsimple/simple_handler.hcefsimple/simple_handler.cc中看到。

浏览器寿命

浏览器的生命周期从调用 CefBrowserHost::CreateBrowser() 或 CefBrowserHost::CreateBrowserSync() 开始。执行此逻辑的方便位置包括 CefBrowserProcessHandler::OnContextInitialized() 回调或特定于平台的消息处理程序,如 Windows 上的 WM_CREATE。

// Information about the window that will be created including parenting, size, etc.
// The definition of this structure is platform-specific.
CefWindowInfo info;
// On Windows for example...
info.SetAsChild(parent_hwnd, client_rect);

// Customize this structure to control browser behavior.
CefBrowserSettings settings;

// CefClient implementation.
CefRefPtr<MyClient> client(new MyClient);

// Create the browser asynchronously. Initially loads the Google URL.
CefBrowserHost::CreateBrowser(info, client.get(), “http://www.google.com”, settings, nullptr, nullptr);

CefLifeSpanHandler类提供管理浏览器生命周期所需的回调。以下是相关方法和成员的摘录。

class MyClient : public CefClient,
                 public CefLifeSpanHandler,
                 ... {
  // CefClient methods.
  virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override {
    return this;
  }

  // CefLifeSpanHandler methods.
  void OnAfterCreated(CefRefPtr<CefBrowser> browser) override;
  bool DoClose(CefRefPtr<CefBrowser> browser) override;
  void OnBeforeClose(CefRefPtr<CefBrowser> browser) override;

  // Member accessors.
  CefRefPtr<CefBrowser> GetBrower() const { return browser_; }
  bool IsClosing() const { return is_closing_; }

 private:
  CefRefPtr<CefBrowser> browser_;
  int browser_id_ = -1;  // invalid value
  int browser_count_ = 0;
  bool is_closing_ = false;

  IMPLEMENT_REFCOUNTING(MyClient);
};

创建浏览器对象后将立即调用 OnAfterCreated() 方法。主机应用程序可以使用此方法来保留对主浏览器对象的引用。

void MyClient::OnAfterCreated(CefRefPtr<CefBrowser> browser) {
  // Must be executed on the UI thread.
  REQUIRE_UI_THREAD();

  if (!browser_) {
    // Keep a reference to the main browser.
    browser_ = browser;
    browser_id_ = browser->GetIdentifier();
  }

  // Keep track of how many browsers currently exist.
  browser_count_++;
}

要销毁浏览器调用 CefBrowserHost::CloseBrowser()。

// Notify the browser window that we would like to close it. This will result in a call to 
// MyHandler::DoClose() if the JavaScript 'onbeforeunload' event handler allows it.
browser->GetHost()->CloseBrowser(false);

如果浏览器是另一个窗口的父窗口,则关闭事件可能源自该父窗口的 OS 函数(例如,通过单击父窗口上的 X)。然后父窗口需要调用 CloseBrowser(false) 并等待第二个操作系统关闭事件以指示浏览器已允许关闭。如果关闭被 JavaScript“onbeforeunload”事件处理程序或 DoClose() 回调取消,则不会发送第二个操作系统关闭事件。请注意以下示例中的 IsClosing() 检查——它将为第一个操作系统关闭事件返回 false,为第二个事件返回 true(在调用 DoClose 之后)。

Windows 上父窗口 WndProc 中的处理:

case WM_CLOSE:
  if (g_handler && !g_handler->IsClosing()) {
    CefRefPtr<CefBrowser> browser = g_handler->GetBrowser();
    if (browser) {
      // Notify the browser window that we would like to close it. This will result in a call to 
      // MyHandler::DoClose() if the JavaScript 'onbeforeunload' event handler allows it.
      browser->GetHost()->CloseBrowser(false);

      // Cancel the close.
      return 0;
    }
  }

  // Allow the close.
  break;

case WM_DESTROY:
  // Quitting CEF is handled in MyHandler::OnBeforeClose().
  return 0;
}

在 Linux 上处理“delete_event”信号:

gboolean delete_event(GtkWidget* widget, GdkEvent* event,
                      GtkWindow* window) {
  if (g_handler && !g_handler->IsClosing()) {
    CefRefPtr<CefBrowser> browser = g_handler->GetBrowser();
    if (browser) {
      // Notify the browser window that we would like to close it. This will result in a call to 
      // MyHandler::DoClose() if the JavaScript 'onbeforeunload' event handler allows it.
      browser->GetHost()->CloseBrowser(false);

      // Cancel the close.
      return TRUE;
    }
  }

  // Allow the close.
  return FALSE;
}

OS X 上的关机更复杂。请参阅cefsimple/cefsimple_mac.mm中的注释,以全面了解关闭在该平台上的工作原理。

DoClose() 方法设置 is_closing_ 标志并返回 false 以发送第二个操作系统关闭事件。

bool MyClient::DoClose(CefRefPtr<CefBrowser> browser) {
  // Must be executed on the UI thread.
  REQUIRE_UI_THREAD();

  // Closing the main window requires special handling. See the DoClose()
  // documentation in the CEF header for a detailed description of this
  // process.
  if (browser_id_ == browser->GetIdentifier()) {
    // Set a flag to indicate that the window close should be allowed.
    is_closing_ = true;
  }

  // Allow the close. For windowed browsers this will result in the OS close
  // event being sent.
  return false;
}

当 OS 函数接收到第二个 OS 关闭事件时,它允许父窗口实际关闭。这会导致调用 OnBeforeClose()。确保在 OnBeforeClose() 回调中释放对浏览器对象的任何引用。

void MyHandler::OnBeforeClose(CefRefPtr<CefBrowser> browser) {
  // Must be executed on the UI thread.
  REQUIRE_UI_THREAD();

  if (browser_id_ == browser->GetIdentifier()) {
    // Free the browser pointer so that the browser can be destroyed.
    browser_ = nullptr;
  }

  if (--browser_count_ == 0) {
    // All browser windows have closed. Quit the application message loop.
    CefQuitMessageLoop();
  }
}

有关每个平台上完整的工作示例,请参阅 cefclient 应用程序。

离屏渲染

对于离屏渲染,CEF 不会创建本机浏览器窗口。相反,CEF 向主机应用程序提供无效区域和像素缓冲区,并且主机应用程序向 CEF 通知鼠标、键盘和焦点等输入事件。离屏渲染目前不支持加速合成,因此与窗口浏览器相比,性能可能会受到影响。离屏浏览器将收到与窗口浏览器相同的通知,包括上一节中描述的生命周期通知。要使用离屏渲染:

  1. 实现CefRenderHandler接口。除非另有说明,否则所有方法都是必需的。
  2. 在将 CefWindowInfo 结构传递给 CefBrowserHost::CreateBrowser() 之前调用 CefWindowInfo::SetAsWindowless()。如果没有父窗口被传递给 SetAsWindowless 一些功能如上下文菜单可能不可用。
  3. CefRenderHandler::GetViewRect() 方法将被调用以检索所需的视图矩形。
  4. CefRenderHandler::OnPaint() 方法将被调用以提供无效区域和更新的像素缓冲区。cefclient 应用程序使用 OpenGL 绘制缓冲区,但您的应用程序可以使用您喜欢的任何技术。
  5. 要调整浏览器大小,请调用 CefBrowserHost::WasResized()。这将导致调用 GetViewRect() 以检索新大小,然后调用 OnPaint()。
  6. 调用 CefBrowserHost::SendXXX() 方法通知浏览器鼠标、键盘和焦点事件。
  7. 调用 CefBrowserHost::CloseBrowser() 来销毁浏览器。

使用“--off-screen-rendering-enabled”命令行标志运行 cefclient 作为一个工作示例。

发布任务

可以使用 CefPostTask 系列方法在单个进程中的不同线程之间发布任务(完整列表请参见include/cef_task.h头文件)。该任务将在目标线程的消息循环中异步执行。

译者注语:这里提供了一种线程间通信的方式,类似Windows平台的SendMessage。项目中使用Windows消息循环或者Qt的消息循环机制即可,或者其他平台相关的线程通信方式。

CEF 提供 base::Bind[Once|Repeating] 和 base::[Once|Repeating]Callback 模板化回调类,用于将绑定方法、对象和参数传递给 CefPostTask。有关完整的 base::Bind 和 base::Callback 用法信息,请参阅Chromium 文档include/wrapper/cef_closure_task.h标头提供了将 base::[Once|Repeating]Closure 转换为 CefTask 的助手。例如:

// Include the necessary headers.
#include “include/base/cef_callback.h”
#include “include/wrapper/cef_closure_task.h”

// To execute a bound function:

// Define a function.
void MyFunc(int arg) { /* do something with |arg| on the UI thread */ }

// Post a task that will execute MyFunc on the UI thread and pass an |arg|
// value of 5.
CefPostTask(TID_UI, base::BindOnce(&MyFunc, 5));

// To execute a bound method:

// Define a class.
class MyClass : public CefBaseRefCounted {
 public:
  MyClass() {}
  void MyMethod(int arg) { /* do something with |arg| on the UI thread */ }
 private:
  IMPLEMENT_REFCOUNTING(MyClass);
};

// Create an instance of MyClass.
CefRefPtr<MyClass> instance = new MyClass();

// Post a task that will execute MyClass::MyMethod on the UI thread and pass
// an |arg| value of 5. |instance| will be kept alive until after the task
// completes.
CefPostTask(TID_UI, base::BindOnce(&MyClass::MyMethod, instance, 5));

如果主机应用程序需要保留对运行循环的引用,它可以使用 CefTaskRunner 类。例如,获取 UI 线程的任务运行器:

CefRefPtr<CefTaskRunner> task_runner = CefTaskRunner::GetForThread(TID_UI);

进程间通信 (IPC)

由于 CEF3 在多个进程中运行,因此有必要提供在这些进程之间进行通信的机制。CefBrowser 和 CefFrame 对象存在于浏览器和渲染进程中,这有助于促进这一过程。每个 CefBrowser 和 CefFrame 对象也有一个与之关联的唯一 ID 值,它将在进程边界的两侧匹配。

处理启动消息

启动数据可以在创建时通过CefBrowserHost::CreateBrowser 的CefRefPtr<CefDictionaryValue> extra_info参数与特定的 CefBrowser 实例相关联。extra_info 数据将通过 CefRenderProcessHandler::OnBrowserCreated 回调传递给与该 CefBrowser 关联的每个渲染器进程。有关何时以及如何生成新渲染器进程的更多信息,请参阅“进程”部分。

处理运行时消息

消息可以在运行时使用CefProcessMessage类在进程之间传递。这些消息与特定的 CefBrowser 和 CefFrame 实例相关联,并使用 CefFrame::SendProcessMessage() 方法发送。进程消息应包含通过 CefProcessMessage::GetArgumentList() 所需的任何状态信息。

// Create the message object.
CefRefPtr<CefProcessMessage> msg= CefProcessMessage::Create(“my_message”);

// Retrieve the argument list object.
CefRefPtr<CefListValue> args = msg>GetArgumentList();

// Populate the argument values.
args->SetString(0, “my string”);
args->SetInt(0, 10);

// Send the process message to the main frame in the render process.
// Use PID_BROWSER instead when sending a message to the browser process.
browser->GetMainFrame()->SendProcessMessage(PID_RENDERER, msg);

从浏览器进程发送到渲染进程的消息将到达 CefRenderProcessHandler::OnProcessMessageReceived()。从渲染进程发送到浏览器进程的消息将到达 CefClient::OnProcessMessageReceived()。

bool MyHandler::OnProcessMessageReceived(
    CefRefPtr<CefBrowser> browser,
    CefRefPtr<CefFrame> frame,
    CefProcessId source_process,
    CefRefPtr<CefProcessMessage> message) {
  // Check the message name.
  const std::string& message_name = message->GetName();
  if (message_name == “my_message”) {
    // Handle the message here...
    return true;
  }
  return false;
}

异步 JavaScript 绑定

JavaScriptIntegration在渲染进程中实现,但经常需要与浏览器进程通信。JavaScript API 本身应该设计为使用闭包承诺异步工作。

通用消息路由器

译者注语:这里给出了一种前端(JavaScript)调用客户端(C++)接口的方法,并且通用消息路由器是一种异步的方式。

CEF 为在渲染器进程中运行的 JavaScript 和在浏览器进程中运行的 C++ 之间的异步消息路由提供了通用实现。应用程序通过将数据从标准的 CEF C++ 回调(OnBeforeBrowse、OnProcessMessageRecieved、OnContextCreated 等)传递给路由器来与之交互。渲染器端的路由器支持通用的 JavaScript 回调注册和执行,而浏览器端的路由器通过一个或多个应用程序提供的处理程序实例支持特定于应用程序的逻辑。有关演示 CefMessageRouter 用法的独立示例应用程序,请参阅message_router 示例。有关完整的使用文档,请参阅include/wrapper/cef_message_router.h


这里官方文档没有直接给出代码示例。因此,译者给出一段使用消息路由器进行异步通信的方式,以作参考。这个示例分为两部分:JavaScript 代码(运行在渲染进程)和 C++ 代码(运行在浏览器进程)。

JavaScript 代码(运行在渲染进程):

// 注册回调函数
window.cefQuery({
  request: 'myRequest', // 这是一个自定义请求字符串,将传递给 C++ 代码
  onSuccess: function(response) {
    // 当 C++ 代码成功处理请求并返回响应时,将执行此函数
    console.log('Received response:', response);
  },
  onFailure: function(error_code, error_message) {
    // 如果请求失败,将执行此函数
    console.error('Request failed. Error code:', error_code, ', message:', error_message);
  }
});

C++ 代码(运行在浏览器进程):
首先,创建一个自定义的请求处理器类,继承自 CefMessageRouterBrowserSide::Handler。

class MyRequestHandler : public CefMessageRouterBrowserSide::Handler {
public:
  // 处理来自渲染进程的请求
  virtual bool OnQuery(CefRefPtr<CefBrowser> browser,
                       CefRefPtr<CefFrame> frame,
                       int64 query_id,
                       const CefString& request,
                       bool persistent,
                       CefRefPtr<Callback> callback) OVERRIDE {
    if (request == "myRequest") {
      // 当收到 "myRequest" 请求时,执行相应的处理

      // 模拟异步操作
      CefPostDelayedTask(TID_UI,
                         base::Bind(&MyRequestHandler::SendSuccessResponse, this, callback),
                         1000); // 延迟 1000 毫秒

      return true; // 表示已处理请求
    }

    return false; // 表示未处理请求
  }

private:
  // 发送成功响应
  void SendSuccessResponse(CefRefPtr<Callback> callback) {
    callback->Success("Hello from C++!"); // 将响应发送回渲染进程
  }

  IMPLEMENT_REFCOUNTING(MyRequestHandler);
};

然后,在 CefClient 派生类中设置消息路由器。

class MyClient : public CefClient, public CefLifeSpanHandler {
public:
  MyClient() {
    // 创建消息路由器
    CefMessageRouterConfig config;
    message_router_ = CefMessageRouterBrowserSide::Create(config);

    // 添加自定义请求处理器
    message_router_->AddHandler(new MyRequestHandler(), false);
  }

  // 省略其他 CefClient 方法的实现

private:
  CefRefPtr<CefMessageRouterBrowserSide> message_router_;

  IMPLEMENT_REFCOUNTING(MyClient);
};

这个简单的示例展示了如何使用 CEF 的消息路由器在 JavaScript 代码(运行在渲染进程)和 C++ 代码(运行在浏览器进程)之间进行异步通信。


自定义实现

译者注语:这里展示了如何通过自定义实现来实现异步 JavaScript 绑定。这个示例没有使用 CEF 的内置消息路由器,而是创建了一个自定义的通信机制。个人觉得有点难以理解,可以暂时不用太纠结这一段内容。

基于 CEF 的应用程序还可以提供其自定义的异步 JavaScript 绑定实现。一个简单的实现可以如下所示:

1. 渲染过程中的 JavaScript 绑定被传递一个回调函数。

// 在 JavaScript 中注册回调函数。
app.setMessageCallback('binding_test', function(name, args) {
  document.getElementById('result').value = "Response: "+args[0];
});

2. 渲染进程保留对回调函数的引用。

// 消息回调映射。
typedef std::map<std::pair<std::string, int>,
                 std::pair<CefRefPtr<CefV8Context>, CefRefPtr<CefV8Value> > >
                 CallbackMap;
CallbackMap callback_map_;

// 在 CefV8Handler::Execute 实现中为“setMessageCallback”。
if (arguments.size() == 2 && arguments[0]->IsString() &&
    arguments[1]->IsFunction()) {
  std::string message_name = arguments[0]->GetStringValue();
  CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
  int browser_id = context->GetBrowser()->GetIdentifier();
  callback_map_.insert(
      std::make_pair(std::make_pair(message_name, browser_id),
                     std::make_pair(context, arguments[1])));
}

3. 渲染进程向浏览器进程发送异步 IPC 消息,请求执行工作。

4. 浏览器进程收到IPC消息并执行工作。

5. 工作完成后,浏览器进程将异步 IPC 消息连同结果发送回渲染进程。

6. 渲染进程收到IPC消息,并根据结果执行回调函数。

// 执行注册的 JavaScript 回调(如果有)。
if (!callback_map_.empty()) {
  const CefString& message_name = message->GetName();
  CallbackMap::const_iterator it = callback_map_.find(
      std::make_pair(message_name.ToString(),
                     browser->GetIdentifier()));
  if (it != callback_map_.end()) {
    // 保留对对象的本地引用。回调可能会从回调映射中移除自身。
    CefRefPtr<CefV8Context> context = it->second.first;
    CefRefPtr<CefV8Value> callback = it->second.second;

    // 进入上下文。
    context->Enter();

    CefV8ValueList arguments;

    // 第一个参数是消息名称。
    arguments.push_back(CefV8Value::CreateString(message_name));

    // 第二个参数是消息参数列表。
    CefRefPtr<CefListValue> list = message->GetArgumentList();
    CefRefPtr<CefV8Value> args = CefV8Value::CreateArray(list->GetSize());
    SetList(list, args);  // Helper function to convert CefListValue to CefV8Value.
    arguments.push_back(args);

    // 执行回调。
    CefRefPtr<CefV8Value> retval = callback->ExecuteFunction(nullptr, arguments);
    if (retval.get()) {
      if (retval->IsBool())
        handled = retval->GetBoolValue();
    }

    // 退出上下文。
    context->Exit();
  }
}

7. 释放与 CefRenderProcessHandler::OnContextReleased() 中的上下文关联的任何 V8 引用。

void MyHandler::OnContextReleased(CefRefPtr<CefBrowser> browser,
                                  CefRefPtr<CefFrame> frame,
                                  CefRefPtr<CefV8Context> context) {
  // 删除已释放上下文的任何已注册的 JavaScript 回调。
  if (!callback_map_.empty()) {
    CallbackMap::iterator it = callback_map_.begin();
    for (; it != callback_map_.end();) {
      if (it->second.first->IsSame(context))
        callback_map_.erase(it++);
      else
        ++it;
    }
  }
}

同步请求

在极少数情况下,可能需要在浏览器和渲染进程之间实现同步通信。应尽可能避免这种情况,因为它会对渲染过程中的性能产生负面影响。但是,如果您必须进行同步通信,请考虑使用同步 XMLHttpRequests,这将在浏览器进程网络层中等待处理时阻塞渲染进程。浏览器进程可以使用自定义方案处理程序或网络拦截来处理请求。有关详细信息,请参阅“网络层”部分。

网络层

默认情况下,CEF3 中的网络请求将以对主机应用程序透明的方式处理。对于希望与网络层建立更紧密关系的应用程序,CEF3 公开了一系列与网络相关的功能。

与网络相关的回调可能发生在不同的线程上,因此请务必注意文档并妥善保护您的数据成员(请参阅“线程”部分了解背景信息)。

自定义请求

在浏览器框架中加载 URL 的最简单方法是通过 CefFrame::LoadURL() 方法。

browser->GetMainFrame()->LoadURL(some_url);

希望发送包含自定义请求标头或上传数据的更复杂请求的应用程序可以使用 CefFrame::LoadRequest() 方法。此方法接受CefRequest对象作为单个参数。

译者注语:这里相当于是提供了一种发送HTTP请求的方式。

警告:LoadRequest 方法将因“错误的 IPC 消息”原因 INVALID_INITIATOR_ORIGIN (213) 而失败,除非您首先使用其他机制(LoadURL、链接点击等)导航到请求源(方案 + 域)。

// Create a CefRequest object.
CefRefPtr<CefRequest> request = CefRequest::Create();

// Set the request URL.
request->SetURL(some_url);

// Set the request method. Supported methods include GET, POST, HEAD, DELETE and PUT.
request->SetMethod(“POST”);

// Optionally specify custom headers.
CefRequest::HeaderMap headerMap;
headerMap.insert(
    std::make_pair("X-My-Header", "My Header Value"));
request->SetHeaderMap(headerMap);

// Optionally specify upload content.
// The default “Content-Type” header value is "application/x-www-form-urlencoded".
// Set “Content-Type” via the HeaderMap if a different value is desired.
const std::string& upload_data = “arg1=val1&arg2=val2”;
CefRefPtr<CefPostData> postData = CefPostData::Create();
CefRefPtr<CefPostDataElement> element = CefPostDataElement::Create();
element->SetToBytes(upload_data.size(), upload_data.c_str());
postData->AddElement(element);
request->SetPostData(postData);

独立于浏览器的请求

应用程序可以通过CefURLRequest类发送网络请求。实现CefURLRequestClient接口来处理结果响应。

  • 与特定 CefBrowser/CefFrame 无关的请求可以通过 CefURLRequest::Create 方法发送。这些类型的请求只能在浏览器进程中使用。
  • 与特定 CefBrowser/CefFrame 关联的请求可以通过 CefFrame::CreateURLRequest 方法发送。这些类型的请求可以在浏览器和渲染器进程中使用。

有关其他使用限制,请参阅有关上述方法的文档。

以下是 CefURLRequest 用法的示例:

class MyRequestClient : public CefURLRequestClient {
 public:
  MyRequestClient()
    : upload_total_(0),
      download_total_(0) {}

  void OnRequestComplete(CefRefPtr<CefURLRequest> request) override {
    CefURLRequest::Status status = request->GetRequestStatus();
    CefURLRequest::ErrorCode error_code = request->GetRequestError();
    CefRefPtr<CefResponse> response = request->GetResponse();

    // Do something with the response...
  }

  void OnUploadProgress(CefRefPtr<CefURLRequest> request,
                        int64 current,
                        int64 total) override {
    upload_total_ = total;
  }

  void OnDownloadProgress(CefRefPtr<CefURLRequest> request,
                          int64 current,
                          int64 total) override {
    download_total_ = total;
  }

  void OnDownloadData(CefRefPtr<CefURLRequest> request,
                      const void* data,
                      size_t data_length) override {
    download_data_ += std::string(static_cast<const char*>(data), data_length);
  }

  bool GetAuthCredentials(bool isProxy,
                          const CefString& host,
                          int port,
                          const CefString& realm,
                          const CefString& scheme,
                          CefRefPtr<CefAuthCallback> callback) override {
    return false;  // Not handled.
  }

 private:
  int64 upload_total_;
  int64 download_total_;
  std::string download_data_;

 private:
  IMPLEMENT_REFCOUNTING(MyRequestClient);
};

发送请求:

// Set up the CefRequest object.
CefRefPtr<CefRequest> request = CefRequest::Create();
// Populate |request| as shown above...

// Create the client instance.
CefRefPtr<MyRequestClient> client = new MyRequestClient();

// Start the request. MyRequestClient callbacks will be executed asynchronously.
CefRefPtr<CefURLRequest> url_request = CefURLRequest::Create(request, client.get(), nullptr);
// To cancel the request: url_request->Cancel();

使用 CefURLRequest 发出的请求也可以通过 CefRequest::SetFlags() 方法指定自定义行为。支持的位标志包括:

  • UR_FLAG_SKIP_CACHE如果设置缓存将在处理请求时被跳过。
  • UR_FLAG_ALLOW_STORED_CREDENTIALS如果设置 cookie 可以随请求一起发送并从响应中保存。
  • UR_FLAG_REPORT_UPLOAD_PROGRESS如果设置上传进度事件,当请求有主体时将生成。
  • UR_FLAG_NO_DOWNLOAD_DATA如果设置 CefURLRequestClient::OnDownloadData 方法将不会被调用。
  • UR_FLAG_NO_RETRY_ON_5XX如果设置 5XX 重定向错误将传播给观察者而不是自动重试。这目前仅适用于源自浏览器进程的请求。

有关受支持标志的完整列表,请参阅cef_urlrequest_flags_t 。

例如,要跳过缓存而不报告下载数据:

request->SetFlags(UR_FLAG_SKIP_CACHE | UR_FLAG_NO_DOWNLOAD_DATA);

请求处理

CEF3 支持两种在应用程序内部处理网络请求的方法。方案处理程序方法允许为针对特定来源(方案+域)的请求注册处理程序。请求拦截方法允许根据应用程序的判断处理任意请求。

警告:使用 HTTP 方案而不是自定义方案可以避免一系列潜在问题。

如果您选择使用自定义方案(“HTTP”、“HTTPS”等以外的任何方案),您必须向 CEF 注册它,以便它按预期运行。如果您希望自定义方案的行为类似于 HTTP(支持 POST 请求并强制实施HTTP 访问控制 (CORS)限制),则应将其注册为“标准”方案。如果您计划对其他方案执行跨域请求或通过 XMLHttpRequest 向您的方案处理程序发送 POST 请求,那么您应该使用 HTTP 方案而不是自定义方案来避免潜在问题。如果您希望使用自定义方案,则通过 CefApp::OnRegisterCustomSchemes() 回调注册属性,该回调必须在所有进程中实现。

void MyApp::OnRegisterCustomSchemes(CefRefPtr<CefSchemeRegistrar> registrar) {
  // Register "client" as a standard scheme.
  int options = CEF_SCHEME_OPTION_STANDARD;
  registrar->AddCustomScheme("client", options);
}

有关受支持标志的完整列表,请参阅cef_scheme_options_t 。

通用资源管理器

CEF 提供了一种通用实现,用于管理来自一个或多个数据源的资源请求。此用户为不同的数据源注册处理程序,例如磁盘上的目录、zip 存档或自定义实现,并且管理器处理请求。应用程序通过从标准 CEF C++ 回调(OnBeforeResourceLoad、GetResourceHandler)传递数据来与路由器交互。有关演示 CefResourceManager 用法的独立示例应用程序,请参阅resource_manager 示例。有关完整的使用文档,请参阅include/wrapper/cef_resource_manager.h

方案处理器(Scheme Handler)

方案处理器通过 CefRegisterSchemeHandlerFactory() 函数注册。调用此函数的好地方是 CefBrowserProcessHandler::OnContextInitialized()。例如,您可以为“client://myapp/”请求注册一个处理程序:

CefRegisterSchemeHandlerFactory("client", “myapp”, new MySchemeHandlerFactory());

处理程序可以与内置方案(HTTP、HTTPS 等)和自定义方案一起使用。使用内置方案时,请选择您的应用程序唯一的域名(如“myapp”或“internal”)。实施CefSchemeHandlerFactoryCefResourceHandler类来处理请求并提供响应数据。如果使用自定义方案,请不要忘记实现 CefApp::OnRegisterCustomSchemes 方法,如上所述。有关演示 CefSchemeHandlerFactory 用法的独立示例应用程序,请参阅scheme_handler 示例。有关完整的使用文档,请参阅include/cef_scheme.h

如果响应数据在请求时是已知的,则CefStreamResourceHandler类提供了 CefResourceHandler 的一个方便的默认实现。

// CefStreamResourceHandler is part of the libcef_dll_wrapper project.
#include “include/wrapper/cef_stream_resource_handler.h”

const std::string& html_content = “<html><body>Hello!</body></html>”;

// Create a stream reader for |html_content|.
CefRefPtr<CefStreamReader> stream =
    CefStreamReader::CreateForData(
        static_cast<void*>(const_cast<char*>(html_content.c_str())),
        html_content.size());

// Constructor for HTTP status code 200 and no custom response headers.
// There’s also a version of the constructor for custom status code and response headers.
return new CefStreamResourceHandler("text/html", stream);

请求拦截

CefRequestHandler::GetResourceHandler() 方法支持拦截任意请求。它使用与方案处理程序方法相同的 CefResourceHandler 类。如果使用自定义方案,请不要忘记实现 CefApp::OnRegisterCustomSchemes 方法,如上所述。

CefRefPtr<CefResourceHandler> MyHandler::GetResourceHandler(
      CefRefPtr<CefBrowser> browser,
      CefRefPtr<CefFrame> frame,
      CefRefPtr<CefRequest> request) {
  // Evaluate |request| to determine proper handling...
  if (...)
    return new MyResourceHandler();

  // Return nullptr for default handling of the request.
  return nullptr;
}

响应过滤

CefRequestHandler::GetResourceResponseFilter() 方法支持过滤请求响应数据。有关工作示例,请参阅cefclient/browser/response_filter_test.cc(可通过测试菜单 > 其他测试 > 响应过滤从 cefclient 示例应用程序访问)。

其他回调

CefRequestHandler接口为各种网络相关事件提供回调,包括身份验证、cookie 处理、外部协议处理、证书错误等

代理解析

代理设置在 CEF3 中使用与 Google Chrome 相同的命令行标志进行配置。

--proxy-server=host:port
      Specify the HTTP/SOCKS4/SOCKS5 proxy server to use for requests. An individual proxy
      server is specified using the format:

        [<proxy-scheme>://]<proxy-host>[:<proxy-port>]

      Where <proxy-scheme> is the protocol of the proxy server, and is one of:

        "http", "socks", "socks4", "socks5".

      If the <proxy-scheme> is omitted, it defaults to "http". Also note that "socks" is equivalent to
      "socks5".

      Examples:

        --proxy-server="foopy:99"
            Use the HTTP proxy "foopy:99" to load all URLs.

        --proxy-server="socks://foobar:1080"
            Use the SOCKS v5 proxy "foobar:1080" to load all URLs.

        --proxy-server="sock4://foobar:1080"
            Use the SOCKS v4 proxy "foobar:1080" to load all URLs.

        --proxy-server="socks5://foobar:66"
            Use the SOCKS v5 proxy "foobar:66" to load all URLs.

      It is also possible to specify a separate proxy server for different URL types, by prefixing
      the proxy server specifier with a URL specifier:

      Example:

        --proxy-server="https=proxy1:80;http=socks4://baz:1080"
            Load https://* URLs using the HTTP proxy "proxy1:80". And load http://*
            URLs using the SOCKS v4 proxy "baz:1080".

--no-proxy-server
      Disables the proxy server.

--proxy-auto-detect
      Autodetect  proxy  configuration.

--proxy-pac-url=URL
      Specify proxy autoconfiguration URL.

如果代理需要身份验证,CefRequestHandler::GetAuthCredentials() 回调将使用 |isProxy| 执行。值为 true 以检索用户名和密码。

bool MyHandler::GetAuthCredentials(
    CefRefPtr<CefBrowser> browser,
    CefRefPtr<CefFrame> frame,
    bool isProxy,
    const CefString& host,
    int port,
    const CefString& realm,
    const CefString& scheme,
    CefRefPtr<CefAuthCallback> callback) {
  if (isProxy) {
    // Provide credentials for the proxy server connection.
    callback->Continue("myuser", "mypass");
    return true;
  }
  return false;
}

由于网络代理解析(例如,如果在 Windows 上选中“自动检测代理设置”),应用程序启动期间的 Web 内容加载可能会延迟。为了获得最佳用户体验,请考虑将您的应用程序设计为首先显示静态启动页面,然后使用元刷新重定向到实际网站——重定向将被阻止,直到代理解析完成。出于测试目的,可以使用“--no-proxy-server”命令行标志禁用代理解析。通过从命令行运行“chrome --url=...”,也可以在 Google Chrome 中复制代理解析延迟。

推荐阅读:
菜鸟与 cef 的邂逅之旅(三):Cef3 中 C++ 与 JavaScript 的互相调用
使用CEF(三)— 从CEF官方Demo源码入手解析CEF架构与CefApp、CefClient对象

相关文章

网友评论

      本文标题:CEF入门必看!CEF官方教程(Wiki)翻译&校对&注语

      本文链接:https://www.haomeiwen.com/subject/ubephdtx.html