cef GeneralUsage

cef Wiki / GeneralUsage


介绍(Introduction)

Chromium 嵌入式框架(CEF)是一个基于Google Chromium的开源项目。不同的是,Chromium项目主要专注于Chrome浏览器的开发,而CEF则专注于促进在第三方应用程序中集成浏览器的使用。CEF通过提供质量稳定的API将用户与底层复杂的Chromium与Blink代码隔离开,跟踪指定的Chromium的发行版来发布分支,以及二进制发行包。CEF中的大多数特性都有默认的实现,他们提供了丰富的功能,基本很少需要或不需要将用户的需求集成其中。目前,在世界各地的企业中将CEF嵌入到他们的产品中的实例已经超过了1亿。在 CEF Wikipedia page中有一些列表会对使用CEF很有用。CEF的用例包括:

  • 在本地应用程序中嵌入一个支持HTML5的浏览器控件。
  • 创建一个用户界面及交互采用web技术的轻量级应用程序。
  • 在应用程序中以自定义的绘制框架利用离屏技术来渲染网页。
  • 对一个已经存在的网站和应用程序的自动化测试。

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

  • 增强的性能与稳定性(Js与插件运行在独立的进程中)
  • 支持Retina显示器
  • 支持WebGL和3D CSS的GPU加速
  • 非常酷的新特性包括WebRTC、语音输入
  • 通过DevTools远程调试协议以及ChromeDriver2提供更好的自动化UI测试
  • 更快地获取当前以及未来web技术特性及标准

这篇文章介绍了使用CEF3来开发一款应用所涉及的常用概念。

开始(Getting Started)

使用发行包(Using a Binary Distribution)

有效的cef3二进制发行包在project Downloads page。页面中包含了在特定平台(Windows, Mac OS X 或者 Linux)编译特定版本cef3的所有必要文件。所有的平台拥有相同结构。

  • cefclient 一个示例工程,展示了最全cef函数使用情况。
  • cefsimple 另一个示例工程,展示了创建一个浏览器所需最简单的cef函数使用情况。
  • Debug 包含了debug版本的libcef库文件以及其它在特定平台所需的库文件。
  • include 包含所有cef头文件。
  • libcef_dll 包含了libcef_dll_wrapper静态库的源代码,使用cef c++ api时必须链接的一个库。更多信息请前往”C++ 封装”章节。
  • Release 包含了release版本的libcef库文件以及其它在特定平台所需的库文件。
  • Resources cef应用程序所需的资源文件(仅Windows和Linux)。这些文件包括.pak文件(资源二进制文件),还有其它依赖于平台的一些文件。

每个二进制发行包都包含有一个README.txt文件,用来描述指定平台下包的详细信息。还有一个LICENSE.txt文件,是一份cef的BSD许可声明。当发布一款基于cef的应用,你应该在你的应用程序中包含对应的cef许可信息。举个例子,你可以在程序界面中的“关于”或者“作者信息”页面中展示这些信息,也可以在程序目录中包含此许可信息文件。

基于cef二进制发行包开发的应用程序可以用各平台的开发工具来编译。包括Windows上的Visual Studio,Mac OS X上的Xcode,Linux上的gcc/make。下载页面中包含了指定二进制包所需要的系统以及编译工具的版本信息。当在Linux上编译时,一定要特别注意列出的工具包依赖项。

关于如何用cef发行包开发一个简单的应用程序,请参考Wiki页面中Tutorial文档。

源代码编译(Building from Source Code)

编译cef源码可以选择用本地的编译工具,也可以用像TeamCity这样的自动化编译系统。当然,我们首先需要通过Git下载Chromium和cef的源码。Chromium源码非常大,编译Chromium源码需要一台高性能的电脑,内存最少需要6G以上。编译Chromium和cef的详细步骤请参考Wiki页面中的BranchesAndBuilding文档

示例应用(Sample Application)

cefclient是一个集成cef客户端程序的完整示例,并且在每个发行包中都包含相应的源代码。开发cef应用程序最简单的方法就是从cefclient程序开始,修改并删除掉你不需要的部分。本文档中的许多例子都是源自于cefclient。

重要概念(Important Concepts)

在开始开发基于cef的应用程序之前,有一些重要的基本概念应该被理解。

C++封装(C++ Wrapper)

libcef动态链接库导出的C接口将用户与cef底层代码以及基础库代码隔离开来。在发行包中,有一个libcef_dll_wrapper工程的源代码,它以C++的方式封装了这些导出的C接口,生成的lib被链接进cefclient示例程序中。C/C++接口转换代码是由translator工具自动生成的。如何使用这些C接口请参考UsingTheCAPI文档。

进程(Processes)

cef3使用多进程框架。主进程负责窗口的创建,绘制,以及网络访问,也叫做“browser”进程。这就是通常所说的宿主应用,大部分的流程逻辑都在browser进程中实现。Blink渲染和js的执行发生在一个独立的叫“render”的进程中。像js绑定和DOM的访问就是render进程中实现。默认的process model会为每个唯一的源主机(协议+域名)产生一个新的render进程。其它的进程会根据需要创建,处理类似Flash插件的进程叫“plugin”进程,处理加速合成的叫“gpu”进程。

默认情况下,主程序可执行文件会被启动多次来运行不同的进程。这个是通过传给CefExecuteProcess函数的参数来控制的。如果主应用程序文件很大,需要很长的时间来加载,或者其它原因不适合非browser进程来执行的情况,可以使用单独的进程文件来执行其它非browser进程。这可以通过CefSettings.browser_subprocess_path变量来设置。请参考“Application Structure”章节获取更多信息。

cef3的进程之间通过IPC(Inter-Process Communication)通信。browser进程和render进程之间可以通过异步的消息进行双向通信。render进程中的JavaScriptIntegration可以导出异步的api用来在browser进程中调用。更多信息请参考“Inter-Process Communication”章节。

指定平台的调试指导请参考连接:WindowsMac OS XLinux

线程(Threads)

cef3框架的每个进程中都是多进程模式。完整的线程列表信息请参考枚举类型cef_thread_id_t。比如browser进程含有以下常用线程:

  • TID_UI 线程是browser进程中的主线程。如果调用CefInitialize()的时候,CefSettings.multi_threaded_message_loop的值为false,这也将是主进程的主线程。
  • TID_IO 线程用于browser进程处理IPC和网络消息。
  • TID_FILE 线程用于browser进程与文件系统的交互。
  • TID_RENDERER 线程是renderer进程的主线程。

由于cef的多线程特性,使用消息传递或锁机制来保证数据在多线程下的访问变得非常重要。CefPostTask系列函数支持在线程之间实现简单的异步消息传递。请参考“Posting Tasks” 章节获取更多信息。

当前线程的类型可以使用CefCurrentlyOn()函数来验证。cef的demo程序中使用下面的一些方法来确保代码在期望的线程中执行。这些都定义在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_THREAD()     DCHECK(CefCurrentlyOn(TID_FILE));
#define CEF_REQUIRE_RENDERER_THREAD() DCHECK(CefCurrentlyOn(TID_RENDERER));

为了支持同步访问,cef提供了base::Lockbase::AutoLock类型,它们定义在include/base/cef_lock.h头文件中,比如:

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

// Class declaration.
class MyClass : public CefBase {
 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_++;
}
引用计数(Reference Counting)

所有的框架类都实现了CefBase接口,所有的实例指针都是使用CefRefPtr智能指针,通过AddRef()Release()来实现自动的引用计数。下面展示了最简单的方法来实现这些类:

class MyClass : public CefBase {
 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();
字符串(Strings)

cef重新定义了数据结构来代表字符串。主要考虑几点原因:

  • libcef动态库和主程序可能使用了不同的运行时来管理堆内存。所有的对象,包括字符串,分配的内存空间需要在相同的运行时下释放。
  • libcef动态库可以支持不同的基本字符串类型(UTF8, UTF16 or wide)。默认使用的是UTF16,你可以修改cef_string.h中的定义并重新编译来更改。如果选择的是wide类型,你需要把不同平台下的分配不同的size的概念牢记于心。

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接口函数来操作cef字符串类型。比如:

  • cef_string_set 对字符串赋值
  • cef_string_clear 将清空字符串
  • cef_string_cmp 将比较字符串

cef还提供了字符串类型(ASCII, UTF8, UTF16 and wide)之间相互转换的函数。请参考cef_string.hcef_string_types.h头文件获取完整的函数列表。

CefString类简化了对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();

使用FromASCII()方法,如果你确定字符串格式是ASCII:

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

一些结构体,类似CefSettings,都有cef_string_t类型的成员。CefString可以用来简化这些成员变量的赋值:

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);
命令行参数(Command Line Arguments)

cef3和chromium中的许多特性都可以用命令行参数来设置。这些参数使用”–some-argument[=optional-param]”的格式并通过CefExecuteProcess()CefMainArgs结构体(详见下面的 “Application Structure” 章节)传递给cef。

  • 禁用命令行参数,在调用CefInitialize()函数之前,将CefSettings.command_line_args_disabled设为true。
  • 指定主应用程序的命令行参数,需要实现CefApp::OnBeforeCommandLineProcessing()方法。
  • 指定传递给子进程的命令行参数,需要实现CefApp::OnBeforeCommandLineProcessing()方法。

请参考shared/common/client_switches.cc来获取更多命令行参数信息。

应用程序布局(Application Layout)

可以根据不同的平台安排不同的布局结构。例如在Mac OS X系统中,程序的布局必须是一个类似app包的结构。而在Windows和 Linux系统中,这个就显示更灵活一些,它允许你以自定义的方式存放cef库文件及资源文件的位置。对于一个完整的程序如何布局,你可以去http://opensource.spotify.com/cefbuilds/index.html下载完整发行包。其中有些文件是必需的,有些文件是可选的。每个文件的详细信息都可以在README.txt文件中找到。

Windows

对于Windows系统,默认的布局是将cef的库文件、资源文件同主应用程序放在一起。目录结构类似这样(2623分支):

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

可以通过设置CefSettings结构体自定义cef库文件及资源文件的位置(详情请参考README.txt文件的“CefSettings”部分)。cefclient示例通过编译资源文件cefclient/resources/win/cefclient.rc的方式加载资源,你也可以使用本地文件来加载。

Linux

在Linux系统中,程序默认的布局是将cef库文件和相关资源文件同主应用程序文件放在一起。但是编译主应用程序时libcef.so文件的位置和发行程序包时的位置是有些差异的。编译链接主应用程序时是由rpath来决定链接库的对应位置的。比如值为 “-Wl,-rpath,.”(“.”表示当前目录)时,将允许你将libcef.so和主程序放在一起。也可以通过LD_LIBRARY_PATH环境变量的值来指定具体的位置。具体目录结构类似这样(2623分支):

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

可以通过设置CefSettings结构体自定义cef库文件及资源文件的位置(详情请参考README.txt文件的“CefSettings”章节)。

Mac OS X

在Mac OS X系统中,应用程序包的布局是由chromium实现代理的,因此不是很灵活。目录结构类似这样(2623分支):

cefclient.app/
    Contents/
        Frameworks/
            Chromium Embedded Framework.framework/
                Chromium Embedded Framework <= main application library
                Resources/
                    cef.pak, devtools_resources.pak, ... <= non-localized resources and strings
                    icudtl.dat <= unicode support data
                    natives_blob.bin, snapshot_blob.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”是unversioned framework,包含所有的cef二进制文件及资源文件。可执行文件(cefclient,cefclient Helper等)通过install_name_tool都被链接进libcef.dylib。

“cefclient Helper”应用程序是用来执行其它独立的进程(renderer, plugin等)。所以它需要一个独立的程序包及info.plist文件。此外,它将不会显示dock图标。

应用程序框架(Application Structure)

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

  • 提供一个入口函数初始化cef,和运行子进程的逻辑或cef消息循环。
  • 提供一个CefApp的实现用来处理指定进程的回调。
  • 提供一个CefClient的实现用来处理浏览器实例的回调。
  • 调用CefBrowserHost::CreateBrowser()创建一个浏览器实例,使用CefLifeSpanHandler来管理浏览器的生命周期。
入口函数(Entry-Point Function)

正如“Processes”章节描述的那样,cef3应用运行多个进程。所有进程可以使用同一个可执行文件,也可以为子进程指定一个独立的可执行文件。进程从入口函数开始执行。指定平台的完整示例请分别参考,Windows:cefclient/cefclient_win.cc,Linux:cefclient/cefclient_gtk.cc,Mac OS-X:cefclient/cefclient_mac.mm

cef创建子进程的时候,会指定命令行的参数信息并通过CefMainArgs结构体传递给CefExecuteProcess函数。CefMainArgs的定义是基于特定平台的。在Linux和Mac OS X中,它接受传进main()函数的argcargv的值。

CefMainArgs main_args(argc, argv);

在Windows中,它接受传进wWinMain()函数的进程实例句柄。这个句柄也可以通过函数GetModuleHandle(NULL)获取。

CefMainArgs main_args(hInstance);
共用可执行文件(Single Executable)

当所有进程共用一个可执行文件时,入口点函数需要根据不同的进程类型来处理。共用一个可执行文件的方式可以用于Windows及Linux系统,但在Mac OS X系统是不被支持的。

// 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);

  // Optional 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;
}
采用独立的可执行文件运行子进程(Separate Sub-Process Executable)

当采用独立的可执行文件运行子进程时,你需要两个独立的工程和两个独立入口点函数。

主进程的入口点函数:

// 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);

  // Optional 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[]) {
  // Structure for passing command-line arguments.
  // The definition of this structure is platform-specific.
  CefMainArgs main_args(argc, argv);

  // Optional 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());
}
消息循环集成(Message Loop Integration)

除了使用cef自己的消息循环,cef也可以集成到已经存在消息循环的工程里。有两种方法可以实现。

  1. 通过定期的调用CefDoMessageLoopWork()来代替调用CefRunMessageLoop()函数。每一次调用CefDoMessageLoopWork()就会执行一次CEF的消息循环。使用这种方式必须格外小心。调用次数过少会饿死CEF消息循环,并且会对浏览器的性能产生不好的影响,调用过多会对cpu的使用率产生影响。
  2. 设置CefSettings.multi_threaded_message_loop = true(仅对Windows)。这将会导致cef在一个独立的线程中运行UI线程,而不是直接使用的主线程。使用这种方式,CefDoMessageLoopWork() 和 CefRunMessageLoop() 都不需要调用。但CefInitialize() 和 CefShutdown()仍然需要在主线程中调用。因此,你将需要自己提供与主线程通信的机制(可以参考cefclient_win.cpp文件中消息使用示例)。你可以在Windows中通过设置命令行参数 “–multi-threaded-message-loop”运行cefclient来测试。
CefSettings

可以通过CefSettings结构体来设置cef应用程序的特性。一些常用配置包括:

  • single_process 为true时采用单进程模式。也可以使用命令行 “single-process”来设置。详情参见“Processes”章节。
  • browser_subprocess_path 独立子进程可执行文件的路径,更多信息请参考“Separate Sub-Process Executable”章节。
  • multi_threaded_message_loop 为true时,在一个独立的线程中运行cef消息循环。更多信息请参考“Message Loop Integration”章节。
  • command_line_args_disabled 为true时,禁用命令行参数来配置浏览器的特性。更多信息请参考“Command Line Arguments”章节。
  • cache_path 存储cache数据的磁盘路径。如果为空,某些功能的cache数据将保存在内存中,还有些其它的会临时保存在磁盘中。像HTML5数据库的localStorage必须指定cache路径才能跨session存储。
  • locale 本地语言设置,字符串会传给Blink。如果为空,默认使用 “en-US”。Linux中会忽略此项,而是根据环境变量的值来确定,解析的顺序为:LANGUAGE, LC_ALL, LC_MESSAGES and LANG. 也可以通过命令行参数 “lang”设置。
  • log_file debug日志的文件路径。如果为空,默认使用 “debug.log”,位于应用程序当前目录中。也可以通过命令行参数 “log-file”来设置。
  • log_severity 日志级别。只有比当前级别高或相同的日志才会被记录。也可以通过命令行参数 “log-severity”来设置,预定义的值包括 “verbose”, “info”, “warning”, “error”, “error-report” 或者 “disable”。
  • resources_dir_path 资源目录的全路径。如果为空, cef.pak和devtools_resources.pak文件必须放到模块同级目录中(Windows/Linux系统),或者app bundle Resources目录中(Mac OS X)。也可以通过命令行参数 “resources-dir-path”来设置。
  • locales_dir_path locales目录的全路径。如果为空,locales目录必须和cef模块处于同级目录。Mac OS X 会忽略此项,它默认会从app bundle Resources目录加载。也可以通过命令行参数”locales-dir-path”来设置此项值。
  • remote_debugging_port 在特定端口启用远程调试,值在1024~65535之间。比如,当此值设为8080时,远程调试url即为http://localhost:8080。你可以选择任意版本的cef及chrome浏览器来调试。也可以通过命令行参数 “remote-debugging-port”来设置此项值。
CefBrowser and CefFrame

CefBrowserCefFrame对象用来发送命令给浏览器以及在回调函数里获取状态信息。每一个CefBrowser对象都有一个主CefFrame对象来代表最顶层的frame,有0个或者多个相关联的CefFrame对象来代表子frame。比如一个浏览器加载的网站中有两个iframe标签的,此时,一个CefBrowser对象会有三个CefFrame对象(一个顶层frame和两个子frame)。

利用浏览器的主frame加载一个URL:

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

浏览器回退

browser->GoBack();

获取主frame的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());

CefBrowserCefFrame对象存在于browser进程以及render进程中。浏览器的操作主要通过CefBrowser::GetHost()方法来掌控。比如,可以用如下方式获取浏览器窗口句柄:

// CefWindowHandle is defined as HWND on Windows, NSView* on Mac OS X
// and GtkWidget* on Linux.
CefWindowHandle window_handle = browser->GetHost()->GetWindowHandle();

其它一些方法可以实现历史导航、加载字符串、请求、发送编辑命令、获取文本内容,更多信息,请查看具体方法的完整文档。

CefApp

CefApp接口提供了指定进程的回调功能。重要回调函数包括:

  • OnBeforeCommandLineProcessing 给以代码编程的方式设置命令行参数提供了机会。更多信息请参考Command Line Arguments”章节。
  • OnRegisterCustomSchemes 提供自定义schemes的机会。更多信息请参考“Request Handling”章节。
  • GetBrowserProcessHandler 返回browser进程功能回调句柄,其中包含OnContextInitialized()方法。
  • GetRenderProcessHandler 返回render进程功能回调句柄。其中包含JavaScript相关的回调以及进程的消息。参考JavaScriptIntegration页面和 “Inter-Process Communication”章节获取更多信息。

一个CefApp的实现示例,可以参考cefsimple/simple_app.hcefsimple/simple_app.cc

CefClient

CefClient接口提供了指定浏览器实例对象的相关回调。一个CefCient对象可以被多个浏览器对象共享。重要的回调包括:

  • 返回浏览器的浏览器生命周期句柄,上下文菜单,对话框,通知显示,拖拽事件,焦点事件,键盘事件等等。多数句柄是可选的。如果对没有实现某个句柄会产生什么影响,请参考cef_client.h中的文档说明。
  • OnProcessMessageReceived 当收到render进程发的ipc消息时被调用。更多信息请参考“Inter-Process Communication”章节。

参考cefsimple/simple_handler.hcefsimple/simple_handler.cc查看CefClient的实现。

浏览器生命周期

浏览器生命周期从调用CefBrowserHost::CreateBrowser()CefBrowserHost::CreateBrowserSync()开始。执行此函数最简单的方法是在CefBrowserProcessHandler::OnContextInitialized()回调函数中调用或在特定的消息响应中调用,比如WM_CREATE(仅Windows)。

// 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, NULL);

CefLifeSpanHandler类提供了管理browser生命周期的必要回调函数。下面是相关代码的提取。

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() { return m_Browser; }
  bool IsClosing() { return m_bIsClosing; }

 private:
  CefRefPtr<CefBrowser> m_Browser;
  int m_BrowserId;
  int m_BrowserCount;
  bool m_bIsClosing;

  IMPLEMENT_REFCOUNTING(MyClient);
};

OnAfterCreated()方法会在browser对象创建后立即调用。应用程序可以在此函数中保存主browser对象的引用。

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

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

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

调用CefBrowserHost::CloseBrowser()销毁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);

如果browser是其它窗口的子窗口,这个关闭事件可能来自于父窗口相应的调用(比如单机了父窗口的关闭按钮,会产生WM_CLOSE)。父窗口需要调用CloseBrowser(false)并且等待第二次的窗口关闭事件,表明browser允许关闭,才能真正去销毁父窗口。如果在JavaScript的 ‘onbeforeunload’事件响应中或者在DoClose()中取消了关闭,第二次的系统关闭事件将不会发送。注意下面示例中的Isclosing()函数–第一次系统关闭事件时将返回false,第二次将返回true(在DoClose()函数被调用之后)。

Windows系统中父窗口窗口过程的处理:

case WM_CLOSE:
  if (g_handler.get() && !g_handler->IsClosing()) {
    CefRefPtr<CefBrowser> browser = g_handler->GetBrowser();
    if (browser.get()) {
      // 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.get() && !g_handler->IsClosing()) {
    CefRefPtr<CefBrowser> browser = g_handler->GetBrowser();
    if (browser.get()) {
      // 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()函数设置了m_bIsClosing并返回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 (m_BrowserId == browser->GetIdentifier()) {
    // Set a flag to indicate that the window close should be allowed.
    m_bIsClosing = true;
  }

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

当系统的窗口函数,收到第二次的关闭事件后,才允许父窗口真正的关闭。此时,browser的生命周期的OnBeforeClose()函数将被调用。在此回调函数中,需确保释放所有浏览器的引用。

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

  if (m_BrowserId == browser->GetIdentifier()) {
    // Free the browser pointer so that the browser can be destroyed.
    m_Browser = NULL;
  }

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

参考cefclient应用查看各个平台下完整工作流程。

离屏渲染(Off-Screen Rendering)

在离屏渲染模式下,浏览器不会创建系统原生的浏览器窗口。cef为宿主程序提供一个无效区域和一块像素缓冲区,宿主程序通知cef鼠标、键盘及焦点事件。离屏渲染目前不支持加速合成,所以性能不能与原生窗口渲染相比。离屏browser将收到与窗口browser相同的事件消息,包括前面章节描述的生命周期。如何使用离屏渲染:

  1. 实现CefRenderHandler接口。所有函数都需要重写,除非特别指明。
  2. 将CefWindowInfo结构体传入CefBrowserHost::CreateBrowser()之前调用CefWindowInfo::SetAsWindowless()。如果没有父窗口传入SetAsWindowless,一些功能比如上下文菜单可能不可用。
  3. CefRenderHandler::GetViewRect()将会被调用,用来获取想要渲染矩形视图。
  4. CefRenderHandler::OnPaint()将会被调用,用来提供无效区域和更新过的像素缓冲区。cefclient应用程序使用OpenGL绘制缓冲区,你也可以使用任何你钟情的技术。
  5. 调用CefBrowserHost::WasResized()来改变browser大小。这将会导致调用GetViewRect()获取新的大小进而调用OnPaint()。
  6. 调用CefBrowserHost::SendXXX()方法来通知browser鼠标,键盘及焦点事件。
  7. 调用CefBrowserHost::CloseBrowser()来销毁browser。

可以用“–off-screen-rendering-enabled”命令行参数运行cefclient查看相应工作流程。

投递任务(Posting Tasks)

使用CefPostTask类方法可以在一个进程中的多个线程中投递task(参考include/cef_task.h头文件获取完整信息)。task会被投递到目标线程的消息队列中异步执行。

CEF提供base::Bindbase::Callback模版类用来绑定函数,对象,以及参数然后传给CefPostTaskbase::Bindbase::Callback的完整用法请参考头文件include/base/cef_callback.hinclude/wrapper/cef_closure_task.h头文件提供了辅助工具将base::Closure转成CefTask。比如:

// Include the necessary headers.
#include “include/base/cef_bind.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::Bind(&MyFunc, 5));

// To execute a bound method:

// Define a class.
class MyClass : public CefBase {
 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::Bind(&MyClass::MyMethod, instance, 5));

如果宿主程序需要一个线程任务队列执行的引用,可以使用CefTaskRunner。比如,获取UI线程的task runner:

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

进程间通信(IPC)

自从cef3采用多进程框架以来,提供进程间的通信机制变得很有必要。CefBrowserCefFrame对象都存在与browser进程和render进程,这也方便了这些进程间的通信。每个 CefBrowserCefFrame对象都有一个唯一的ID和它们相关联,可以在进程间对他们进行匹配。

进程启动时消息(Process Startup Messages)

在主进程中实现CefBrowserProcessHandler::OnRenderProcessThreadCreated()方法为所有render进程提供相同的启动信息。这将会把信息传递给render进程中的CefRenderProcessHandler::OnRenderThreadCreated()方法。

进程运行时消息(Process Runtime Messages)

在进程运行的过程中使用CefProcessMessage类来传递消息。这些消息与特定的CefBrowser实例相关联,并且使用CefBrowser::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 render process.
// Use PID_BROWSER instead when sending a message to the browser process.
browser->SendProcessMessage(PID_RENDERER, msg);

browser进程发送给render进程的消息将会到达CefRenderProcessHandler::OnProcessMessageReceived()。render进程发送给browser进程的消息将会到达CefClient::OnProcessMessageReceived()

bool MyHandler::OnProcessMessageReceived(
    CefRefPtr<CefBrowser> browser,
    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;
}

发送消息的时候,可以将指定的CefFrame的frame id(通过CefFrame::GetIdentifier()获取)作为消息参数来与消息相关联。在接收消息进程中通过CefBrowser::GetFrame()方法来获取相关联的CefFrame

// Helper macros for splitting and combining the int64 frame ID value.
#define MAKE_INT64(int_low, int_high) \
    ((int64) (((int) (int_low)) | ((int64) ((int) (int_high))) << 32))
#define LOW_INT(int64_val) ((int) (int64_val))
#define HIGH_INT(int64_val) ((int) (((int64) (int64_val) >> 32) & 0xFFFFFFFFL))

// Sending the frame ID.
const int64 frame_id = frame->GetIdentifier();
args->SetInt(0, LOW_INT(frame_id));
args->SetInt(1, HIGH_INT(frame_id));

// Receiving the frame ID.
const int64 frame_id = MAKE_INT64(args->GetInt(0), args->GetInt(1));
CefRefPtr<CefFrame> frame = browser->GetFrame(frame_id);
异步Js绑定(Asynchronous JavaScript Bindings)

JavaScriptIntegration在render进程中实现,但时经常需要与browser进程交互。JavaScript APIs应该被设计成可以使用closurespromises的异步工作模式。

通用消息路由(Generic Message Router)

CEF为render进程中运行的JavaScript代码和browser进程中运行的c++代码之间传递异步消息提供了通用的实现。应用程序通过标准的cef C++回调函数来传递数据路由交互(OnBeforeBrowse, OnProcessMessageRecieved, OnContextCreated等)。renderer侧的路由支持通用的JavaScript 回调注册及执行,而browser侧的路由通过一个或多个应用程序提供的句柄实例支持应用程序指定的逻辑。参考message_router example,一个独立的示例应用,展示了CefMessageRouter的使用方法。参考include/wrapper/cef_message_router.h获取完整的使用文档。

自定义实现(Custom Implementation)

基于cef的应用程序也提供了自定义异步绑定JavaScript的方法。下面是简单的实现:

  • 在render进程中绑定的JavaScript传入的是一个回调函数。
// In JavaScript register the callback function.
app.setMessageCallback('binding_test', function(name, args) {
  document.getElementById('result').value = "Response: "+args[0];
});
  • render进程保存了一个回调函数的引用。
// Map of message callbacks.
typedef std::map<std::pair<std::string, int>,
                 std::pair<CefRefPtr<CefV8Context>, CefRefPtr<CefV8Value> > >
                 CallbackMap;
CallbackMap callback_map_;

// In the CefV8Handler::Execute implementation for “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])));
}
  • render进程给browser进程发送异步ipc消息请求执行。
  • browser进程接收消息开始工作。
  • 完成后,browser进程将结果通过ipc回发给render进程。
  • render进程接受ipc消息并用得到的结果执行回调函数。
// Execute the registered JavaScript callback if any.
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()) {
    // Keep a local reference to the objects. The callback may remove itself
    // from the callback map.
    CefRefPtr<CefV8Context> context = it->second.first;
    CefRefPtr<CefV8Value> callback = it->second.second;

    // Enter the context.
    context->Enter();

    CefV8ValueList arguments;

    // First argument is the message name.
    arguments.push_back(CefV8Value::CreateString(message_name));

    // Second argument is the list of message arguments.
    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);

    // Execute the callback.
    CefRefPtr<CefV8Value> retval = callback->ExecuteFunction(NULL, arguments);
    if (retval.get()) {
      if (retval->IsBool())
        handled = retval->GetBoolValue();
    }

    // Exit the context.
    context->Exit();
  }
}
  • 在CefRenderProcessHandler::OnContextReleased()释放和上下文关联的V8引用。
void MyHandler::OnContextReleased(CefRefPtr<CefBrowser> browser,
                                  CefRefPtr<CefFrame> frame,
                                  CefRefPtr<CefV8Context> context) {
  // Remove any JavaScript callbacks registered for the context that has been released.
  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;
    }
  }
}
同步请求(Synchronous Requests)

在有些场合,可能需要在browser进程和render进程之间进行同步通信。应该尽量避免这种情况,因为这会对render进程的性能产生消极的影响。如果一定要使用同步通信,请考虑使用synchronous XMLHttpRequests,它会在browser进程网络层等待处理的时候阻塞render进程。browser进程可以使用自定义的scheme句柄或网络拦截来处理请求。详情请参考“Network Layer”章节。

网络层(Network Layer)

默认情况下,cef3的网络请求对宿主程序来说是以一种透明的方式被处理。想要密切的了解网络层,cef3导出了一系列网络相关的函数。

网络相关的回调函数会出现在不同的线程中,注意文档的说明并保护好你的数据成员。

自定义请求(Custom Requests)

通过CefFrame::LoadURL(),加载一个url。

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

应用程序可能想要发送更复杂的请求,包括自定义请求头或者上传数据时,可以使用CefFrame::LoadRequest()方法。此函数接受CefRequest对象作为唯一参数。

// 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);
浏览器独立请求(Browser-Independent Requests)

应用程序通过使用CefURLRequest类,可以不用关联特定browser来发送网络请求。实现CefURLRequestClient接口来处理响应结果。CefURLRequest在browser进程和render进程中都可以使用。

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

  virtual 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...
  }

  virtual void OnUploadProgress(CefRefPtr<CefURLRequest> request,
                                uint64 current,
                                uint64 total) OVERRIDE {
    upload_total_ = total;
  }

  virtual void OnDownloadProgress(CefRefPtr<CefURLRequest> request,
                                  uint64 current,
                                  uint64 total) OVERRIDE {
    download_total_ = total;
  }

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

 private:
  uint64 upload_total_;
  uint64 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());
// To cancel the request: url_request->Cancel();

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

  • UR_FLAG_SKIP_CACHE 跳过cache处理请求。
  • UR_FLAG_ALLOW_CACHED_CREDENTIALS 请求和响应都会使用cookie。
  • UR_FLAG_REPORT_UPLOAD_PROGRESS 当请求有body时便会产生上传过程事件。
  • UR_FLAG_NO_DOWNLOAD_DATA 不会调用CefURLRequestClient::OnDownloadData。
  • UR_FLAG_NO_RETRY_ON_5XX 5XX重定向错误会被传给观察者,而不是自动进行重定向。这个现在仅针对browser进程发出的请求。

比如,跳过cache并且不用通知下载数据:

request->SetFlags(UR_FLAG_SKIP_CACHE | UR_FLAG_NO_DOWNLOAD_DATA);
请求处理(Request Handling)

cef3支持两种途径处理网络请求。一种是scheme handler,它允许为指定的源(scheme + domain)注册一个请求。另一种是请求拦截,它可以按照应用程序的意图操作任意的请求。

使用HTTP scheme代替自定义scheme来避免一些潜在的问题。

如果你选择使用自定义scheme(除 “HTTP”, “HTTPS”之外),你必须先进行注册,然后才能正常使用。如果你想要自定义的scheme跟HTTP有相似的行为,你应该将它注册为一个“标准的”scheme。如果你想要跨域请求或者通过XMLHttpRequest发送POST请求,你应该使用HTTP scheme代替自定义scheme来避免potential issues。如果你想使用自定义scheme,需要通过 CefApp::OnRegisterCustomSchemes()回调中注册,所有进程中都必须实现。

void MyApp::OnRegisterCustomSchemes(CefRefPtr<CefSchemeRegistrar> registrar) {
  // Register "client" as a standard scheme.
  registrar->AddCustomScheme("client", true, ...);
}
通用资源管理(Generic Resource Manager)

cef对管理资源请求提供了通用的实现方法。用户为不同的数据注册句柄,例如磁盘目录,zip文件,或者自定义实现,管理对象处理这些请求。应用程序从标准的cef C++回调函数中获取数据并将他们传递给路由来进行交互(OnBeforeResourceLoad, GetResourceHandler)。参考resource_manager example,一个独立的示例,展示了CefResourceManager的使用方法。查看CefResourceManager获取完整的使用文档。

Scheme句柄(Scheme Handler)

一个Scheme句柄是通过CefRegisterSchemeHandlerFactory()函数注册的。在CefBrowserProcessHandler::OnContextInitialized()中调用此函数是一个不错的选择。例如,你可以注册一个”client://myapp/”请求的句柄:

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

可以使用内置schemes(HTTP, HTTPS等)和自定义scheme。当使用内置scheme时选择一个唯一的域名(比如“myapp” 或 “internal”)。实现CefSchemeHandlerFactoryCefResourceHandler类处理请求以及提供响应数据。如果使用自定义scheme,别忘了实现CefApp::OnRegisterCustomSchemes方法。参考scheme_handler example获取CefSchemeHandlerFactory的使用方法。查看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);
请求拦截(Request Interception)

CefRequestHandler::GetResourceHandler()函数支持对任意请求的拦截。和scheme handler使用相同的CefResourceHandler类。如果使用自定义scheme,别忘了实现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 NULL for default handling of the request.
  return NULL;
}
响应过滤(Response Filtering)

CefRequestHandler::GetResourceResponseFilter()方法支持对请求响应数据进行过滤。参考cefclient/browser/response_filter_test.cc查看工作流程。

其它回调

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;
}

应用程序启动时会因为使用网络代理而使加载网页内容有所延时。为了更好的用户体验,你可以考虑在应用启动时先显示一个静态的页面,然后使用meta refresh重定向到实际的网页–重定向会被阻塞直到代理加载完成。测试时可以使用命令行参数 “–no-proxy-server” 来禁用代理。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值