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开发中涉及到的一般概念。
开始
- 从源码编译(building from source code)
- 示例应用程序(sample application)
- 重要概念(important concepts)
- c++ 封装(c++ wrapper)
- 应用程序布局(application layout)
- 应用程序结构(application structure)
- 单一执行体(single executable)
- 分离子进程执行体(separate sub-process executable)
- 集成消息循环(message loop integration)
- cefsettings
- cefbrowser和cefframe
- cefapp
- cefclient
- browser生命周期(browser life span)
- 离屏渲染(off-screen rendering)
- 投递任务(posting tasks)
- 进程间通信(inter-process communication (ipc))
- 处理启动消息(process startup messages)
- 处理运行时消息(process runtime messages)
- 异步javascript绑定(asynchronous javascript bindings)
- 同步请求(synchronous requests)
- 网络层(network layer)
- 自定义请求(custom requests)
- 浏览器无关请求(browser-independent requests)
- 请求响应(request handling)
- scheme响应(scheme handler)
- 请求拦截(request interception)
- 其他回调(other callbacks)
- proxy resolution
使用二进制包
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可以不用它自己提供的消息循环,而与已经存在的程序中消息环境集成在一起,有两种方式可以做到:
周期性执行cefdomessageloopwork()函数,替代调用cefrunmessageloop()。cefdomessageloopwork()的每一次调用,都将执行一次cef消息循环的单次迭代。需要注意的是,此方法调用次数太少时,cef消息循环会饿死,将极大的影响browser的性能,调用次数太频繁又将影响cpu使用率。
设置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相关的一些回调以及消息处理的回调。
更多细节,请参考javascriptintegration和inter-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绑定。典型的实现如下:
- render进程的javascript传递一个回调函数。
// in javascript register the callback function. app.setmessagecallback('binding_test', function(name, args) { document.getelementbyid('result').value = "response: "+args[0]; });
- 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]))); }
render进程发送异步进程间通信到browser进程。
browser进程接收到进程间消息,并处理。
browser进程处理完毕后,发送一个异步进程间消息给render进程,返回结果。
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(); } }
- 在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=...
.
上一篇: PHP下载大文件