当前位置: 移动技术网 > IT编程>开发语言>C/C++ > Cef3 学习资料

Cef3 学习资料

2019年06月11日  | 移动技术网IT编程  | 我要评论

东莞常平桑拿,ca3650,北京市政府采购网

cef general usage(cef3预览)

cef general usage(cef3预览)

介绍

cef全称chromium embedded framework,是一个基于google chromium 的开源项目。google chromium项目主要是为google chrome应用开发的,而cef的目标则是为第三方应用提供可嵌入浏览器支持。cef隔离底层chromium和blink的复杂代码,并提供一套产品级稳定的api,发布跟踪具体chromium版本的分支,以及二进制包。cef的大部分特性都提供了丰富的默认实现,让使用者做尽量少的定制即可满足需求。在本文发布的时候,世界上已经有很多公司和机构采用cef,cef的安装量超过了100万。[cef wikipedia]页面上有使用cef的公司和机构的不完全的列表。cef的典型应用场景包括:

  • 嵌入一个兼容html5的浏览器控件到一个已经存在的本地应用。
  • 创建一个轻量化的壳浏览器,用以托管主要用web技术开发的应用。
  • 有些应用有独立的绘制框架,使用cef对web内容做离线渲染。
  • 使用cef做自动化web测试。

cef3是基于chomuim content api多进程构架的下一代cef,拥有下列优势:

  • 改进的性能和稳定性(javascript和插件在一个独立的进程内执行)。
  • 支持retina显示器。
  • 支持webgl和3d css的gpu加速。
  • 类似webrtc和语音输入这样的前卫特性。
  • 通过devtools远程调试协议以及chromedriver2提供更好的自动化ui测试。
  • 更快获得当前以及未来的web特性和标准的能力。

本文档介绍cef3开发中涉及到的一般概念。

开始

使用二进制包

cef3的二进制包可以在。其中包含了在特定平台(windows,mac os x 以及 linux)编译特定版本cef3所需的全部文件。不同平台拥有共同的结构:

  • cefclient
  • debug
  • include
  • libcef_dll
  • release
  • resources
  • tools

每个二进制包包含一个readme.txt文件和一个license.txt文件,readme.txt用以描述平台相关的细节,而license.txt包含cef的bsd版权说明。如果你发布了基于cef的应用,则应该在应用程序的某个地方包含该版权声明。例如,你可以在"关于”和“授权"页面列出该版权声明,或者单独一个文档包含该版权声明。“关于”和“授权”信息也可以分别在cef浏览器的"about:license"和"about:credits"页面查看。

基于cef二进制包的应用程序可以使用每个平台上的经典编译工具。包括windows平台上的visual studio,mac osx平台上的xcode,以及linux平台上的gcc/make编译工具链。cef项目的下载页面包含了这些平台上编译特定版本cef所需的编译工具的版本信息。在linux上编译cef时需要特别注意依赖工具链。

tutorial wiki页面有更多关于如何使用cef3二进制包创建简单应用程序的细节。

从源码编译(building from source code)

cef可以从源码编译,用户可以使用本地编译系统或者像teamcity这样的自动化编译系统编译。首先你需要使用svn或者git下载chromium和cef的源码。由于chromium源码很大,只建议在内存大于4gb的现代机器上编译。编译chromium和cef的细节请参考branchesandbuilding页面。

示例应用程序(sample application)

cefclient是一个完整的cef客户端应用程序示例,并且它的源码包含在cef每个二进制发布包中。使用cef创建一个新的应用程序,最简单的方法是先从cefclient应用程序开始,删除你不需要的部分。本文档中许多示例都是来源于cefclient应用程序。

重要概念(important concepts)

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

c++ 封装(c++ wrapper)

libcef 动态链接库导出 c api 使得使用者不用关心cef运行库和基础代码。libcef_dll_wrapper 工程把 c api 封装成 c++ api同时包含在客户端应用程序工程中,与cefclient一样,源代码作为cef二进制发布包的一部分共同发布。c/c++ api的转换层代码是由转换工具自动生成。usingthecapi 页面描述了如何使用c api。

进程(processes)

cef3是多进程架构的。browser被定义为主进程,负责窗口管理,界面绘制和网络交互。blink的渲染和js的执行被放在一个独立的render
进程中;除此之外,render进程还负责js binding和对dom节点的访问。
默认的进程模型中,会为每个标签页创建一个新的render进程。其他进程按需创建,例如管理插件的进程以及处理合成加速的进程等都是按需创建。

默认情况下,主应用程序会被多次启动运行各自独立的进程。这是通过传递不同的命令行参数给cefexecuteprocess函数做到的。如果主应用程序很大,加载时间比较长,或者不能在非浏览器进程里使用,则宿主程序可使用独立的可执行文件去运行这些进程。这可以通过配置cefsettings.browser_subprocess_path变量做到。更多细节请参考application structure一节。

cef3的进程之间可以通过ipc进行通信。browser和render进程可以通过发送异步消息进行双向通信。甚至在render进程可以注册在browser进程响应的异步javascript api。
更多细节,请参考inter-process communication一节。

通过设置命令行的--single-process,cef3就可以支持用于调试目的的单进程运行模型。支持的平台为:windows,mac os x 和linux。

线程(threads)

在cef3中,每个进程都会运行多个线程。完整的线程类型表请参照cef_thread_id_t。例如,在browser进程中包含如下主要的线程:

  • tid_ui 线程是浏览器的主线程。如果应用程序在调用调用cefinitialize()时,传递cefsettings.multi_threaded_message_loop=false,这个线程也是应用程序的主线程。
  • tid_io 线程主要负责处理ipc消息以及网络通信。
  • tid_file 线程负责与文件系统交互。

由于cef采用多线程架构,有必要使用锁和闭包来保证数据的线程安全语义。implement_locking定义提供了lock()和unlock()方法以及autolock对象来保证不同代码块同步访问数据。cefposttask函数组支持简易的线程间异步消息传递。更多信息,请参考posting tasks章节。

可以通过cefcurrentlyon()方法判断当前所在的线程环境,cefclient工程使用下面的定义来确保方法在期望的线程中被执行。

#define require_ui_thread()   assert(cefcurrentlyon(tid_ui));
#define require_io_thread()   assert(cefcurrentlyon(tid_io));
#define require_file_thread() assert(cefcurrentlyon(tid_file));
引用计数(reference counting)

所有的框架类从cefbase继承,实例指针由cefrefptr管理,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以及wide)。默认采用的是utf16,默认字符集可以通过更改cef_string.h文件中的定义,然后重新编译来修改。当使用宽字节集的时候,切记字符的长度由当前使用的平台决定。

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来设置常用的字符编码。

typedef char16 cef_char_t;
typedef cef_string_utf16_t cef_string_t;

cef提供了一批c语言的方法来操作字符串(通过#define的方式来适应不同的字符编码)

  • cef_string_set 对制定的字符串变量赋值(支持深拷贝或浅拷贝)。
  • cef_string_clear 清空字符串。
  • cef_string_cmp 比较两个字符串。

cef也提供了字符串不同编码之间相互转换的方法。具体函数列表请查阅cef_string.h和cef_string_types.h文件。

在c++中,通常使用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();

如果是ascii编码,使用fromascii进行赋值:

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结构(参考下面的章节)传递给cef。在传递cefsettings结构给cefinitialize()之前,我们可以设置cefsettings.command_line_args_disabled为true来禁用对命令行参数的处理。如果想指定命令行参数传入主应用程序,实现cefapp::onbeforecommandlineprocessing()方法。更多关于如何查找已支持的命令行选项的信息,请查看client_switches.cpp文件的注释。

应用程序布局(application layout)

应用资源布局依赖于平台,有很大的不同。比如,在mac os x上,你的资源布局必须遵循特定的app bundles结构;window与linux则更灵活,允许你定制cef库文件与资源文件所在的位置。为了获取到特定可以正常工作的示例,你可以从工程的下载页面下载到一个client压缩包。每个平台对应的readme.txt文件详细说明了哪些文件是可选的,哪些文件是必须的。

windows操作系统(windows)

在windows平台上,默认的资源布局将libcef库文件、相关资源与可执行文件放置在同级目录,文件夹结构大致如下:

application/
    cefclient.exe  <= cefclient application executable 
    libcef.dll <= main cef library 
    icudt.dll <= icu unicode support library 
    ffmpegsumo.dll <= html5 audio/video support library 
    libegl.dll, libglesv2.dll, … <= accelerated compositing support libraries 
    cef.pak, devtools_resources.pak <= non-localized resources and strings 
    locales/
        en-us.pak, … <= locale-specific resources and strings 

使用结构体cefsettings可以定制cef库文件、资源文件的位置(查看readme.txt文件或者本文中cefsettings部分获取更详细的信息)。虽然在windows平台上,cefclient项目将资源文件以二进制形式编译进cefclient.rc文件,但是改为从文件系统加载资源也很容易。

linux操作系统(linux)

在linux平台上,默认的资源布局将libcef库文件、相关资源与可执行文件放置在同级目录。注意:在你编译的版本与发行版本应用程序中,libcef.so的位置是有差异的,此文件的位置取决于编译可执行程序时,编译器rpath的值。比如,编译选项为“-wl,-rpath,.”(“.”意思是当前文件夹),这样libcef.so与可执行文件处于同级目录。libcef.so文件的路径可以通过环境变量中的“ld_library_path”指定。

application/
    cefclient  <= cefclient application executable 
    libcef.so <= main cef library 
    ffmpegsumo.so <-- html5 audio/video support library 
    cef.pak, devtools_resources.pak <= non-localized resources and strings 
    locales/
        en-us.pak, … <= locale-specific resources and strings 
    files/
        binding.html, … <= cefclient application resources 

使用结构体cefsettings可以定制cef库文件、资源文件(查看readme.txt文件或者本文中cefsettings部分获取更详细的信息)。

mac x平台(mac os x)

在mac x平台上,app bundles委托给了chromium实现,因此不是很灵活。文件夹结构大致如下:

cefclient.app/
    contents/
        frameworks/
            chromium embedded framework.framework/
                libraries/
                    ffmpegsumo.so <= html5 audio/video support library 
                    libcef.dylib <= main cef library
                resources/
                    cef.pak, devtools_resources.pak <= non-localized resources and strings
                    *.png, *.tiff <= blink image and cursor resources 
                    en.lproj/, … <= locale-specific resources and strings 
            libplugin_carbon_interpose.dylib <= plugin support library
            cefclient helper.app/
                contents/
                    info.plist
                    macos/
                        cefclient helper <= helper executable 
                    pkginfo
            cefclient helper eh.app/
                contents/
                    info.plist
                    macos/
                        cefclient helper eh <= helper executable 
                    pkginfo
            cefclient helper np.app/
                contents/
                    info.plist
                    macos/
                        cefclient helper np <= helper executable 
                    pkginfo
        info.plist
        macos/
            cefclient <= cefclient application executable 
        pkginfo
        resources/
            binding.html, … <= cefclient application resources 

列表中的“chromium embedded framework.framework”,这个未受版本管控的框架包含了所有的cef库文件、资源文件。使用install_name_tool与@executable_path,将cefclient,cefclient helper等可执行文件,连接到了libcef.dylib上。

应用程序cefclient helper用来执行不同特点、独立的进程(renderer,plugin等),这些进程需要独立的资源布局与info.plist等文件,它们没有显示停靠图标。用来启动插件进程的eh helper清除了mh_no_heap_execution标志位,这样就允许一个可执行堆。只能用来启动nacl插件进程的np helper,清除了mh_pie标志位,这样就禁用了aslr。这些都是tools文件夹下面,用来构建进程脚本的一部分。为了理清脚本的依赖关系,更好的做法是检查发行版本中的xcode工程或者原始文件cefclient.gyp。

应用程序结构(application structure)

每个cef3应用程序都是相同的结构

  • 提供入口函数,用于初始化cef、运行子进程执行逻辑或者cef消息循环。
  • 提供cefapp实现,用于处理进程相关的回调。
  • 提供cefclient实现,用于处理browser实例相关的回调。
  • 执行cefbrowserhost::createbrowser()创建一个browser实例,使用ceflifespanhandler管理browser对象生命周期。
入口函数(entry-point function)

像本文中进程章节描述的那样,一个cef3应用程序会运行多个进程,这些进程能够使用同一个执行器或者为子进程定制的、单独的执行器。进程的执行从入口函数开始,示例cefclient_win.cc、cefclient_gtk.cc、cefclient_mac.mm分别对应windows、linux和mac os-x平台下的实现。

当执行子进程时,cef将使用命令行参数指定配置信息,这些命令行参数必须通过cefmainargs结构体传入到cefexecuteprocess函数。cefmainargs的定义与平台相关,在linux、mac os x平台下,它接收main函数传入的argc和argv参数值。

cefmainargs main_args(argc, argv);

在windows平台下,它接收wwinmain函数传入的参数:实例句柄(hinstance),这个实例能够通过函数getmodulehandle(null)获取。

cefmainargs main_args(hinstance);

单一执行体(single executable)

当以单一执行体运行时,根据不同的进程类型,入口函数有差异。windows、linux平台支持单一执行体架构,mac os x平台则不行。

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)

当使用独立的子进程执行体时,你需要2个分开的可执行工程和2个分开的入口函数。

主程序的入口函数:

// 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.
  // 可选择性地实现cefapp接口
  cefrefptr<myapp> app(new myapp);

  // populate this structure to customize cef behavior.
  // 填充这个结构体,用于定制cef的行为。
  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.
  // 在主进程中初始化cef 
  cefinitialize(main_args, settings, app.get());

  // run the cef message loop. this will block until cefquitmessageloop() is called.
  // 执行消息循环,此时会堵塞,直到cefquitmessageloop()函数被调用。
  cefrunmessageloop();

  // shut down cef.
  // 关闭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.
  // 可选择性地实现cefapp接口
  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可以不用它自己提供的消息循环,而与已经存在的程序中消息环境集成在一起,有两种方式可以做到:

  1. 周期性执行cefdomessageloopwork()函数,替代调用cefrunmessageloop()。cefdomessageloopwork()的每一次调用,都将执行一次cef消息循环的单次迭代。需要注意的是,此方法调用次数太少时,cef消息循环会饿死,将极大的影响browser的性能,调用次数太频繁又将影响cpu使用率。

  2. 设置cefsettings.multi_threaded_message_loop=true(windows平台下有效),这个设置项将导致cef在单独的线程上运行browser的界面,而不是在主线程上,这种场景下cefdomessageloopwork()或者cefrunmessageloop()都不需要调用,cefinitialze()、cefshutdown()仍然在主线程中调用。你需要提供主程序线程通信的机制(查看cefclient_win.cpp中提供的消息窗口实例)。在windows平台下,你可以通过命令行参数--multi-threaded-message-loop测试上述消息模型。

cefsettings

cefsettings结构体允许定义全局的cef配置,经常用到的配置项如下:

  • single_process 设置为true时,browser和renderer使用一个进程。此项也可以通过命令行参数“single-process”配置。查看本文中“进程”章节获取更多的信息。
  • browser_subprocess_path 设置用于启动子进程单独执行器的路径。参考本文中章节获取更多的信息。
  • cache_path 设置磁盘上用于存放缓存数据的位置。如果此项为空,某些功能将使用内存缓存,多数功能将使用临时的磁盘缓存。形如本地存储的html5数据库只能在设置了缓存路径才能跨session存储。
  • locale 此设置项将传递给blink。如果此项为空,将使用默认值“en-us”。在linux平台下此项被忽略,使用环境变量中的值,解析的依次顺序为:languae,lc_all,lc_messages和lang。此项也可以通过命令行参数“lang”配置。
  • log_file 此项设置的文件夹和文件名将用于输出debug日志。如果此项为空,默认的日志文件名为debug.log,位于应用程序所在的目录。此项也可以通过命令参数“log-file”配置。
  • log_severity 此项设置日志级别。只有此等级、或者比此等级高的日志的才会被记录。此项可以通过命令行参数“log-severity”配置,可以设置的值为“verbose”,“info”,“warning”,“error”,“error-report”,“disable”。
  • resources_dir_path 此项设置资源文件夹的位置。如果此项为空,windows平台下cef.pak、linux平台下devtools_resourcs.pak、mac os x下的app bundle resources目录必须位于组件目录。此项也可以通过命令行参数“resource-dir-path”配置。
  • locales_dir_path 此项设置locale文件夹位置。如果此项为空,locale文件夹必须位于组件目录,在mac os x平台下此项被忽略,pak文件从app bundle resources目录。此项也可以通过命令行参数“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对象代表页面的顶层frame;同时每个cefbrowser对象可以包含零个或多个的cefframe对象,分别代表不同的子frame。例如,一个浏览器加载了两个iframe,则该cefbrowser对象拥有三个cefframe对象(顶层frame和两个iframe)。

下面的代码在浏览器的主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());

cefbrowser和cefframe对象在browser进程和render进程都有对等的代理对象。在browser进程里,host(宿主)行为控制可以通过cefbrowser::gethost()方法控制。例如,浏览器窗口的原生句柄可以用下面的代码获取:

// cefwindowhandle is defined as hwnd on windows, nsview* on mac os x
// and gtkwidget* on linux.
cefwindowhandle window_handle = browser->gethost()->getwindowhandle();

其他方法包括历史导航,加载字符串和请求,发送编辑命令,提取text/html内容等。请参考支持函数相关的文档或者cefbrowser的头文件注释。

cefapp

cefapp接口提供了不同进程的可定制回调函数。毕竟重要的回调函数如下:

  • onbeforecommandlineprocessing 提供了以编程方式设置命令行参数的机会,更多细节,请参考command line arguments一节。
  • onregistercustomschemes 提供了注册自定义schemes的机会,更多细节,请参考request handling一节。
  • getbrowserprocesshandler 返回定制browser进程的handler,该handler包括了诸如oncontextinitialized的回调。
  • getrenderprocesshandler 返回定制render进程的handler,该handler包含了javascript相关的一些回调以及消息处理的回调。
    更多细节,请参考javascriptintegrationinter-process communication两节。

cefapp子类的例子:

// myapp implements cefapp and the process-specific interfaces.
class myapp : public cefapp,
              public cefbrowserprocesshandler,
              public cefrenderprocesshandler {
 public:
  myapp() {}

  // cefapp methods. important to return |this| for the handler callbacks.
  virtual void onbeforecommandlineprocessing(
      const cefstring& process_type,
      cefrefptr<cefcommandline> command_line) {
    // programmatically configure command-line arguments...
  }
  virtual void onregistercustomschemes(
      cefrefptr<cefschemeregistrar> registrar) override {
    // register custom schemes...
  }
  virtual cefrefptr<cefbrowserprocesshandler> getbrowserprocesshandler()
      override { return this; }
  virtual cefrefptr<cefrenderprocesshandler> getrenderprocesshandler()
      override { return this; }

  // cefbrowserprocesshandler methods.
  virtual void oncontextinitialized() override {
    // the browser process ui thread has been initialized...
  }
  virtual void onrenderprocessthreadcreated(cefrefptr<ceflistvalue> extra_info)
                                            override {
    // send startup information to a new render process...
  }

  // cefrenderprocesshandler methods.
  virtual void onrenderthreadcreated(cefrefptr<ceflistvalue> extra_info)
                                     override {
    // the render process main thread has been initialized...
    // receive startup information in the new render process...
  }
  virtual void onwebkitinitialized(cefrefptr<clientapp> app) override {
    // webkit has been initialized, register v8 extensions...
  }
  virtual void onbrowsercreated(cefrefptr<cefbrowser> browser) override {
    // browser created in this render process...
  }
  virtual void onbrowserdestroyed(cefrefptr<cefbrowser> browser) override {
    // browser destroyed in this render process...
  }
  virtual bool onbeforenavigation(cefrefptr<cefbrowser> browser,
                                  cefrefptr<cefframe> frame,
                                  cefrefptr<cefrequest> request,
                                  navigationtype navigation_type,
                                  bool is_redirect) override {
    // allow or block different types of navigation...
  }
  virtual void oncontextcreated(cefrefptr<cefbrowser> browser,
                                cefrefptr<cefframe> frame,
                                cefrefptr<cefv8context> context) override {
    // javascript context created, add v8 bindings here...
  }
  virtual void oncontextreleased(cefrefptr<cefbrowser> browser,
                                 cefrefptr<cefframe> frame,
                                 cefrefptr<cefv8context> context) override {
    // javascript context released, release v8 references here...
  }
  virtual bool onprocessmessagereceived(
      cefrefptr<cefbrowser> browser,
      cefprocessid source_process,
      cefrefptr<cefprocessmessage> message) override {
    // handle ipc messages from the browser process...
  }

  implement_refcounting(myapp);
};

cefclient

cefclient提供访问browser实例的回调接口。一个cefclient实现可以在任意数量的browser进程中共享。以下为几个重要的回调:

  • 比如处理browser的生命周期,右键菜单,对话框,通知显示, 拖曳事件,焦点事件,键盘事件等等。如果没有对某个特定的处理接口进行实现会造成什么影响,请查看cef_client.h文件中相关说明。
  • onprocessmessagereceived在browser收到render进程的消息时被调用。更多细节,请参考inter-process communication一节。

cefclient子类的例子:

// myhandler implements cefclient and a number of other interfaces.
class myhandler : public cefclient,
                  public cefcontextmenuhandler,
                  public cefdisplayhandler,
                  public cefdownloadhandler,
                  public cefdraghandler,
                  public cefgeolocationhandler,
                  public cefkeyboardhandler,
                  public ceflifespanhandler,
                  public cefloadhandler,
                  public cefrequesthandler {
 public:
  myhandler();

  // cefclient methods. important to return |this| for the handler callbacks.
  virtual cefrefptr<cefcontextmenuhandler> getcontextmenuhandler() override {
    return this;
  }
  virtual cefrefptr<cefdisplayhandler> getdisplayhandler() override {
    return this;
  }
  virtual cefrefptr<cefdownloadhandler> getdownloadhandler() override {
    return this;
  }
  virtual cefrefptr<cefdraghandler> getdraghandler() override {
    return this;
  }
  virtual cefrefptr<cefgeolocationhandler> getgeolocationhandler() override {
    return this;
  }
  virtual cefrefptr<cefkeyboardhandler> getkeyboardhandler() override {
    return this;
  }
  virtual cefrefptr<ceflifespanhandler> getlifespanhandler() override {
    return this;
  }
  virtual cefrefptr<cefloadhandler> getloadhandler() override {
    return this;
  }
  virtual cefrefptr<cefrequesthandler> getrequesthandler() override {
    return this;
  }
  virtual bool onprocessmessagereceived(cefrefptr<cefbrowser> browser,
                                        cefprocessid source_process,
                                        cefrefptr<cefprocessmessage> message)
                                        override {
    // handle ipc messages from the render process...
  }

  // cefcontextmenuhandler methods
  virtual void onbeforecontextmenu(cefrefptr<cefbrowser> browser,
                                   cefrefptr<cefframe> frame,
                                   cefrefptr<cefcontextmenuparams> params,
                                   cefrefptr<cefmenumodel> model) override {
    // customize the context menu...
  }
  virtual bool oncontextmenucommand(cefrefptr<cefbrowser> browser,
                                    cefrefptr<cefframe> frame,
                                    cefrefptr<cefcontextmenuparams> params,
                                    int command_id,
                                    eventflags event_flags) override {
    // handle a context menu command...
  }

  // cefdisplayhandler methods
  virtual void onloadingstatechange(cefrefptr<cefbrowser> browser,
                                    bool isloading,
                                    bool cangoback,
                                    bool cangoforward) override {
    // update ui for browser state...
  }
  virtual void onaddresschange(cefrefptr<cefbrowser> browser,
                               cefrefptr<cefframe> frame,
                               const cefstring& url) override {
    // update the url in the address bar...
  }
  virtual void ontitlechange(cefrefptr<cefbrowser> browser,
                             const cefstring& title) override {
    // update the browser window title...
  }
  virtual bool onconsolemessage(cefrefptr<cefbrowser> browser,
                                const cefstring& message,
                                const cefstring& source,
                                int line) override {
    // log a console message...
  }

  // cefdownloadhandler methods
  virtual void onbeforedownload(
      cefrefptr<cefbrowser> browser,
      cefrefptr<cefdownloaditem> download_item,
      const cefstring& suggested_name,
      cefrefptr<cefbeforedownloadcallback> callback) override {
    // specify a file path or cancel the download...
  }
  virtual void ondownloadupdated(
      cefrefptr<cefbrowser> browser,
      cefrefptr<cefdownloaditem> download_item,
      cefrefptr<cefdownloaditemcallback> callback) override {
    // update the download status...
  }

  // cefdraghandler methods
  virtual bool ondragenter(cefrefptr<cefbrowser> browser,
                           cefrefptr<cefdragdata> dragdata,
                           dragoperationsmask mask) override {
    // allow or deny drag events...
  }

  // cefgeolocationhandler methods
  virtual void onrequestgeolocationpermission(
      cefrefptr<cefbrowser> browser,
      const cefstring& requesting_url,
      int request_id,
      cefrefptr<cefgeolocationcallback> callback) override {
    // allow or deny geolocation api access...
  }

  // cefkeyboardhandler methods
  virtual bool onprekeyevent(cefrefptr<cefbrowser> browser,
                             const cefkeyevent& event,
                             cefeventhandle os_event,
                             bool* is_keyboard_shortcut) override {
    // perform custom handling of key events...
  }

  // ceflifespanhandler methods
  virtual bool onbeforepopup(cefrefptr<cefbrowser> browser,
                             cefrefptr<cefframe> frame,
                             const cefstring& target_url,
                             const cefstring& target_frame_name,
                             const cefpopupfeatures& popupfeatures,
                             cefwindowinfo& windowinfo,
                             cefrefptr<cefclient>& client,
                             cefbrowsersettings& settings,
                             bool* no_javascript_access) override {
    // allow or block popup windows, customize popup window creation...
  }
  virtual void onaftercreated(cefrefptr<cefbrowser> browser) override {
    // browser window created successfully...
  }
  virtual bool doclose(cefrefptr<cefbrowser> browser) override {
    // allow or block browser window close...
  }
  virtual void onbeforeclose(cefrefptr<cefbrowser> browser) override {
    // browser window is closed, perform cleanup...
  }

  // cefloadhandler methods
  virtual void onloadstart(cefrefptr<cefbrowser> browser,
                           cefrefptr<cefframe> frame) override {
    // a frame has started loading content...
  }
  virtual void onloadend(cefrefptr<cefbrowser> browser,
                         cefrefptr<cefframe> frame,
                         int httpstatuscode) override {
    // a frame has finished loading content...
  }
  virtual void onloaderror(cefrefptr<cefbrowser> browser,
                           cefrefptr<cefframe> frame,
                           errorcode errorcode,
                           const cefstring& errortext,
                           const cefstring& failedurl) override {
    // a frame has failed to load content...
  }
  virtual void onrenderprocessterminated(cefrefptr<cefbrowser> browser,
                                         terminationstatus status) override {
    // a render process has crashed...
  }

  // cefrequesthandler methods
  virtual cefrefptr<cefresourcehandler> getresourcehandler(
      cefrefptr<cefbrowser> browser,
      cefrefptr<cefframe> frame,
      cefrefptr<cefrequest> request) override {
    // optionally intercept resource requests...
  }
  virtual bool onquotarequest(cefrefptr<cefbrowser> browser,
                              const cefstring& origin_url,
                              int64 new_size,
                              cefrefptr<cefquotacallback> callback) override {
    // allow or block quota requests...
  }
  virtual void onprotocolexecution(cefrefptr<cefbrowser> browser,
                                   const cefstring& url,
                                   bool& allow_os_execution) override {
    // handle execution of external protocols...
  }

  implement_refcounting(myhandler);
};

browser生命周期(browser life span)

browser生命周期从执行 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);

the ceflifespanhandler class provides the callbacks necessary for managing browser life span. below is an extract of the relevant methods and members.

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(myhandler);
  implement_locking(myhandler);
};

当browser对象创建后onaftercreated() 方法立即执行。宿主程序可以用这个方法来保持对browser对象的引用。

void myclient::onaftercreated(cefrefptr<cefbrowser> browser) {
  // must be executed on the ui thread.
  require_ui_thread();
  // protect data members from access on multiple threads.
  autolock lock_scope(this);

  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对象的关闭事件来源于他的父窗口的关闭方法(比如,在父窗口上点击x控钮。)。父窗口需要调用 closebrowser(false) 并且等待操作系统的第二个关闭事件来决定是否允许关闭。如果在javascript 'onbeforeunload'事件处理或者 doclose()回调中取消了关闭操作,则操作系统的第二个关闭事件可能不会发送。注意一下面示例中对iscloseing()的判断-它在第一个关闭事件中返回false,在第二个关闭事件中返回true(当 docloase 被调用后)。

windows平台下,在父窗口的wndproc里处理wm_close消息:

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

macos x平台下,处理windowshouldclose选择器:

// called when the window is about to close. perform the self-destruction
// sequence by getting rid of the window. by returning yes, we allow the window
// to be removed from the screen.
- (bool)windowshouldclose:(id)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 no;
    }
  }

  // try to make the window go away.
  [window autorelease];

  // clean ourselves up after clearing the stack of anything that might have the
  // window on it.
  [self performselectoronmainthread:@selector(cleanup:)
                         withobject:window
                      waituntildone:no];

  // allow the close.
  return yes;
}

doclose方法设置m_blsclosing 标志位为true,并返回false以再次发送操作系统的关闭事件。

bool myclient::doclose(cefrefptr<cefbrowser> browser) {
  // must be executed on the ui thread.
  require_ui_thread();
  // protect data members from access on multiple threads.
  autolock lock_scope(this);

  // 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()) {
    // notify the browser that the parent window is about to close.
    browser->gethost()->parentwindowwillclose();

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

当操作系统捕捉到第二次关闭事件,它才会允许父窗口真正关闭。该动作会先触发onbeforeclose()回调,请在该回调里释放所有对浏览器对象的引用。

void myhandler::onbeforeclose(cefrefptr<cefbrowser> browser) {
  // must be executed on the ui thread.
  require_ui_thread();
  // protect data members from access on multiple threads.
  autolock lock_scope(this);

  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为宿主程序提供无效的区域和像素缓存区,而宿主程序负责通知鼠标键盘以及焦点事件给cef。离屏渲染目前不支持混合加速,所以性能上可能无法和非离屏渲染相比。离屏浏览器将收到和窗口浏览器同样的事件通知,例如前一节介绍的生命周期事件。下面介绍如何使用离屏渲染:

  • 实现cefrenderhandler接口。除非特别说明,所有的方法都需要覆写。
  • 调用cefwindowinfo::setasoffscreen(),将cefwindowinfo传递给cefbrowserhost::createbrowser()之前还可以选择设置cefwindowinfo::settransparentpainting()。如果没有父窗口被传递给setasoffscreen,则有些类似上下文菜单这样的功能将不可用。
  • cefrenderhandler::getviewrect方法将被调用以获得所需要的可视区域。
  • cefrenderhandler::onpaint() 方法将被调用以提供无效区域(脏区域)以及更新过的像素缓存。cefclient程序里使用opengl绘制缓存,但你可以使用任何别的绘制技术。
  • 可以调用cefbrowserhost::wasresized()方法改变浏览器大小。这将导致对getviewrect()方法的调用,以获取新的浏览器大小,然后调用onpaint()重新绘制。
  • 调用cefbrowserhost::sendxxx()方法通知浏览器的鼠标、键盘和焦点事件。
  • 调用cefbrowserhost::closebrowser()销毁浏览器。

使用命令行参数--off-screen-rendering-enabled运行cefclient,可以测试离屏渲染的效果。

投递任务(posting tasks)

任务(task)可以通过cefposttask在一个进程内的不同的线程之间投递。cefposttask有一系列的重载方法,详细内容请参考cef_task.h头文件。任务将会在被投递线程的消息循环里异步执行。例如,为了在ui线程上执行myobject::mymethod方法,并传递两个参数,代码如下:

cefposttask(tid_ui, newcefrunnablemethod(object, &myobject::mymethod, param1, param2));

为了在io线程在执行myfunction方法,同时传递两个参数,代码如下:

cefposttask(tid_io, newcefrunnablefunction(myfunction, param1, param2));

参考cef_runnable.h头文件以了解更多关于newcefrunnable模板方法的细节。

如果宿主程序需要保留一个运行循环的引用,则可以使用ceftaskrunner类。例如,获取ui线程的任务运行器(task runner),代码如下:

cefrefptr<ceftaskrunner> task_runner = ceftaskrunner::getforthread(tid_ui);

进程间通信(inter-process communication (ipc))

由于cef3运行在多进程环境下,所以需要提供一个进程间通信机制。cefbrowser和cefframe对象在borwser和render进程里都有代理对象。cefbrowser和cefframe对象都有一个唯一id值绑定,便于在两个进程间定位匹配的代理对象。

处理启动消息(process startup messages)

为了给所有的render进程提供一样的启动信息,请在browser进程实现cefbrowserprocesshander::onrenderprocessthreadcreated()方法。在这里传入的信息会在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::geridentifier()获取cefframe的id,并通过进程间消息发送给另一个进程,然后在接收端通过cefbrowser::getframe()找到对应的cefframe。通过这种方式可以将进程间消息和特定的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);

异步javascript绑定(asynchronous javascript bindings)

javascript被集成在render进程,但是需要频繁和browser进程交互。 javascript api应该被设计成可使用闭包异步执行。

通用消息转发(generic message router)

从1574版本开始,cef提供了在render进程执行的javascript和在browser进程执行的c++代码之间同步通信的转发器。应用程序通过c++回调函数(onbeforebrowse, onprocessmessagerecieved, oncontextcreated等)传递数据。render进程支持通用的javascript回调函数注册机制,browser进程则支持应用程序注册特定的handler进行处理。

下面的代码示例在javascript端扩展window对象,添加cefquery函数:

// create and send a new query.
var request_id = window.cefquery({
    request: 'my_request',
    persistent: false,
    onsuccess: function(response) {},
    onfailure: function(error_code, error_message) {}
});

// optionally cancel the query.
window.cefquerycancel(request_id);

对应的c++ handler代码如下:

class callback : public cefbase {
 public:
  ///
  // notify the associated javascript onsuccess callback that the query has
  // completed successfully with the specified |response|.
  ///
  virtual void success(const cefstring& response) =0;

  ///
  // notify the associated javascript onfailure callback that the query has
  // failed with the specified |error_code| and |error_message|.
  ///
  virtual void failure(int error_code, const cefstring& error_message) =0;
};

class handler {
 public:
  ///
  // executed when a new query is received. |query_id| uniquely identifies the
  // query for the life span of the router. return true to handle the query
  // or false to propagate the query to other registered handlers, if any. if
  // no handlers return true from this method then the query will be
  // automatically canceled with an error code of -1 delivered to the
  // javascript onfailure callback. if this method returns true then a
  // callback method must be executed either in this method or asynchronously
  // to complete the query.
  ///
  virtual bool onquery(cefrefptr<cefbrowser> browser,
                       cefrefptr<cefframe> frame,
                       int64 query_id,
                       const cefstring& request,
                       bool persistent,
                       cefrefptr<callback> callback) {
    return false;
  }

  ///
  // executed when a query has been canceled either explicitly using the
  // javascript cancel function or implicitly due to browser destruction,
  // navigation or renderer process termination. it will only be called for
  // the single handler that returned true from onquery for the same
  // |query_id|. no references to the associated callback object should be
  // kept after this method is called, nor should any callback methods be
  // executed.
  ///
  virtual void onquerycanceled(cefrefptr<cefbrowser> browser,
                               cefrefptr<cefframe> frame,
                               int64 query_id) {}
};

完整的用法请参考wrapper/cef_message_router.h

自定义实现(custom implementation)

一个cef应用程序也可以提供自己的异步javascript绑定。典型的实现如下:

  1. render进程的javascript传递一个回调函数。
// in javascript register the callback function.
app.setmessagecallback('binding_test', function(name, args) {
  document.getelementbyid('result').value = "response: "+args[0];
});
  1. render进程的c++端通过一个map持有javascript端注册的回调函数。
// 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])));
}
  1. render进程发送异步进程间通信到browser进程。

  2. browser进程接收到进程间消息,并处理。

  3. browser进程处理完毕后,发送一个异步进程间消息给render进程,返回结果。

  4. render进程接收到进程间消息,则调用最开始保存的javascript注册的回调函数处理之。

// 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();
  }
}
  1. 在cefrenderprocesshandler::oncontextreleased()里释放javascript注册的回调函数以及其他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进程的性能造成负面影响。然而如果你一定要做进程间同步通信,可以考虑使用xmlhttprequest,xmlhttprequest在等待browser进程的网络响应的时候会等待。browser进程可以通过自定义scheme handler或者网络交互处理xmlhttprequest。更多细节,请参考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类发送和浏览器无关的网络请求。并实现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();

可以通过cefrequest::setflags定制请求的行为,这些标志位包括:

  • ur_flag_skip_cache 如果设置了该标志位,则处理请求响应时,缓存将被跳过。
  • ur_flag_allow_cached_credentials 如果设置了该标志位,则可能会发送cookie并在响应端被保存。同时ur_flag_allow_cached_credentials标志位也必须被设置。
  • ur_flag_report_upload_progress 如果设置了该标志位,则当请求拥有请求体时,上载进度事件将会被触发。
  • ur_flag_report_load_timing 如果设置了该标志位,则时间信息会被收集。
  • ur_flag_report_raw_headers 如果设置了该标志位,则头部会被发送,并且接收端会被记录。
  • ur_flag_no_download_data 如果设置了该标志位,则cefurlrequestclient::ondownloaddata方法不会被调用。
  • ur_flag_no_retry_on_5xx 如果设置了该标志位,则5xx重定向错误会被交给相关observer去处理,而不是自动重试。这个功能目前只能在browser进程的请求端使用。

例如,为了跳过缓存并不报告下载数据,代码如下:

request->setflags(ur_flag_skip_cache | ur_flag_no_download_data);

请求响应(request handling)

cef3 支持两种方式处理网络请求。一种是实现scheme handler,这种方式允许为特定的(sheme+domain)请求注册特定的请求响应。另一种是请求拦截,允许处理任意的网络请求。

注册自定义scheme(有别于http,https等)可以让cef按希望的方式处理请求。例如,如果你希望特定的shceme被当策划那个http一样处理,则应该注册一个standard的scheme。如果你的自定义shceme可被跨域执行,则应该考虑使用使用http scheme代替自定义scheme以避免潜在问题。如果你希望使用自定义scheme,实现cefapp::onregistercustomschemes回调。

void myapp::onregistercustomschemes(cefrefptr<cefschemeregistrar> registrar) {
  // register "client" as a standard scheme.
  registrar->addcustomscheme("client", true, false, false);
}

scheme响应(scheme handler)

通过cefregisterschemehandlerfactory方法注册一个scheme响应,最好在cefbrowserprocesshandler::oncontextinitialized()方法里调用。例如,你可以注册一个"client://myapp/"的请求:

cefregisterschemehandlerfactory("client", “myapp”, new myschemehandlerfactory());

scheme handler类可以被用在内置shcme(http,https等),也可以被用在自定义scheme上。当使用内置shceme,选择一个对你的应用程序来说唯一的域名。实现cefschemehandlerfactory和cefresourehandler类去处理请求并返回响应数据。可以参考cefclient/sheme_test.h的例子。

// implementation of the factory for creating client request handlers.
class myschemehandlerfactory : public cefschemehandlerfactory {
 public:
  virtual cefrefptr<cefresourcehandler> create(cefrefptr<cefbrowser> browser,
                                               cefrefptr<cefframe> frame,
                                               const cefstring& scheme_name,
                                               cefrefptr<cefrequest> request)
                                               override {
    // return a new resource handler instance to handle the request.
    return new myresourcehandler();
  }

  implement_refcounting(myschemehandlerfactory);
};

// implementation of the resource handler for client requests.
class myresourcehandler : public cefresourcehandler {
 public:
  myresourcehandler() {}

  virtual bool processrequest(cefrefptr<cefrequest> request,
                              cefrefptr<cefcallback> callback)
                              override {
    // evaluate |request| to determine proper handling...
    // execute |callback| once header information is available.
    // return true to handle the request.
    return true;
  }

  virtual void getresponseheaders(cefrefptr<cefresponse> response,
                                  int64& response_length,
                                  cefstring& redirecturl) override {
    // populate the response headers.
    response->setmimetype("text/html");
    response->setstatus(200);

    // specify the resulting response length.
    response_length = ...;
  }

  virtual void cancel() override {
    // cancel the response...
  }

  virtual bool readresponse(void* data_out,
                            int bytes_to_read,
                            int& bytes_read,
                            cefrefptr<cefcallback> callback)
                            override {
    // read up to |bytes_to_read| data into |data_out| and set |bytes_read|.
    // if data isn't immediately available set bytes_read=0 and execute
    // |callback| asynchronously.
    // return true to continue the request or false to complete the request.
    return …;
  }

 private:
  implement_refcounting(myresourcehandler);
};

如果响应数据类型是已知的,则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()方法支持拦截任意请求。参考client_handler.cpp。

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

其他回调(other callbacks)

cefrequesthander接口还提供了其他回调函数以定制其他网络相关事件。包括授权、cookie处理、外部协议处理、证书错误等。

proxy resolution

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禁用代理并做相关测试。代理延迟也可以通过chrome浏览器重现,方式是使用命令行传参:chrome -url=....

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网