CEF 的进程模型
CEF 使用的是 chromium 内核,下图是chromium浏览器的进程模型。方框代表进程,连接线代表IPC进程间通信。
| 进程类型 | 描述 |
|---|---|
| Browser 进程 | 宿主进程,有且只有1个,负责处理窗口的创建、绘制、网络交互等绝大多数主要逻辑 |
| Render 进程 | 网页渲染进程,blink 渲染和执行 js 代码(blink 是谷歌的排版引擎) 模型会为每个唯一源(协议+域)创建至少一个 render 进程 1、如果打开了多个子窗口,访问相同的源,它们会共享 render 进程 2、如果每个子窗口访问的源都不一样,则会为每个源都创建一个 render 进程 3、如果窗口中改变了访问源,则进程模型会将原有 render 进程杀掉,创建新的 render 进程 |
| GPU 加速进程 | 按需创建,最多只有1个,只有 GPU 硬件加速打开的时候才被创建 |
| NPAPI 插件进程 | 按需创建,每种类型的插件只会有一个进程,每个插件进程可以被多个 Render 进程共享 |
| Pepper 插件进程 | 同 NPAPI 插件进程,不同的是为 Pepper 插件而创建的进程 |
| 其它进程 | 进程模型会根据需要创建其它的进程,我们不太需要关注这些进程 |
CEF 的使用中,我们主要实现 Browser 进程和 Render 进程。其它进程可以交由框架去处理。
CEF 的多线程模型
为了保证用户的的高响应度,CEF采用了多线程模型,保证程序的实时响应,如下图
1、Broswer 进程收到用户请求,首先由UI线程处理,将相应的任务转交给 IO 线程,IO线程处理后将任务传递给 Render 进程。 2、Render 进程的 IO 线程经过简单的解释后交给渲染线程,渲染线程接收请求加载网页并渲染,渲染完成后再给由 IO 线程通知 Browser 进程。 3、Browser 进程将收到结果并将结果绘制出来。
CEF C++类的组织构架
先看几个关键类的层次结构:
- CefV8Context 类: V8 引擎上下文,用于执行 JS 代码,和维护 JS 环境中的所有资源
- CefFrame 类: 表示浏览器窗口中的一个 frame,HTML 页面可以由很多 frame 组成,每个 frame 都有自己的 URL 或者一段 HTML 代码,可通过该类获取页面的源码、文本、URL、V8 执行上下文、访问页面中的 DOM等。
- CefBrowser 类: 该类代表一个浏览器对象,管理浏览器的 CefFrame 列表、前进、后退、刷新等。
- CefClient 类: 响应页面的各种事件,如 CefBrowser 生命周期回调、右键菜单、对话框、状态通知显示、下载事件、拖曳事件、焦点事件、键盘事件,离屏渲染事件等
- CefBrowserHost 类: 表示一个 CefBrowser 所处的进程,所以它可以被 browser 进程中的任意线程调用,但绝不能在其他进程中使用。相比较于 CefBrowser 类,CefBrowserHost 提供了更多功能接口,如窗体句柄、窗体事件、DevTools、文件对话框等。
- CefBrowserView 类: 表示一个视图,视图需要吸附在一个窗体 (CefWindow) 上。
- CefWindow 类: 表示一个窗体控件,可以控制窗体的显示与隐藏、大小、全屏等。一个窗体可以添加多个 CefBrowserView 视图。
C++ 调用 JS
由 CEF 类层次关系图可以看出来,C++ 层想要执行 JS 代码,需要在V8引擎的上下文中执行。
CefFrame 层封装了一个接口ExecuteJavaScript,用于执行 JS 代码,示例如下:
CefRefPtr<CefBrowser> browser = ...;
CefRefPtr<CefFrame> frame = browser->GetMainFrame();
frame->ExecuteJavaScript("alert('Hello World')", ...);
ExecuteJavaScript 缺点:函数返回值为空,函数中也没有其他形式获取 JS 的返回值
JS 调用 C++
C++ 想被 JS 层调用,必须将自己映射到 JS 环境中,这里只涉及到一个类:CefV8Value,如下图所示:
CefV8Value可以理解成 Qt 的 QVariant、boost 中的boost::any。可以将 C++ 任意类型数据赋值给它。
JS 调用 C++ 的本质,就是 C++ 将自己的打包成 CefV8Value中,并注册到 JS 环境中。这样一来,JS 层就可以直接对其进行读写或调用了。
示例1:绑定 C++ 变量到 JS 层的window 对象上
void CefRenderProcessHandler::OnContextCreated(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefV8Context> context)
{
CefRefPtr<CefV8Value> object = context->GetGlobal();// 获取到js的window对象句柄
CefRefPtr<CefV8Value> str = CefV8Value::CreateString("Hello World");
object->SetValue("myval", str, V8_PROPERTY_ATTRIBUTE_NONE);
}
示例2:感知 C++ 变量被 JS 读写
class MyV8Accessor : public CefV8Accessor {
public:
MyV8Accessor() {}
virtual bool Get(const CefString& name,
const CefRefPtr<CefV8Value> object,
CefRefPtr<CefV8Value>& retval,
CefString& exception)
{
retval = CefV8Value::CreateString(m_myval);
return true;
}
virtual bool Set(const CefString& name,
const CefRefPtr<CefV8Value> object,
const CefRefPtr<CefV8Value> value,
CefString& exception)
{
if (value.IsString()) {
m_myval = value.GetStringValue();
} else {
exception = "Invalid value type";
}
return true;
}
}
private:
CefString m_myval;
};
CefRefPtr<CefV8Accessor> accessor = new MyV8Accessor();
CefRefPtr<CefV8Value> obj = CefV8Value::CreateObject(accessor);
示例3:绑定 C++ 函数到 JS 层
class MyV8Handler : public CefV8Handler {
public:
virtual bool Execute(const CefString& name,
CefRefPtr<CefV8Value> object,
const CefV8ValueList& arguments,
CefRefPtr<CefV8Value>& retval,
CefString& exception)
{
retval = CefV8Value::CreateString("My Value!");
return true;
};
CefRefPtr<CefV8Handler> hander = new MyV8Handler();
CefRefPtr<CefV8Value> func = CefV8Value::CreateFunction("myfunc", handler);
示例4(不推荐):将预定义的代码,注册到 JS 环境中(优先考虑直接将代码写在 JS 里)
void MyRenderProcessHandler::OnWebKitInitialized() {
std::string extensionCode =
"var test;"
"if (!test)"
" test = {};"
"(function() {"
" test.myval = 'My Value!';"
"})();";
CefRegisterExtension("v8/test", extensionCode, NULL);
}
CEF 库文件说明
- libcef.dll: CEF核心库
- chrome_elf.dll: 崩溃报告
- icudtl.dat: Unicode支持数据
- snapshot_blob.bin: V8快照数据
下方文件,负责HTML5加速,如2D画布,3D CSS和WebGL等:
- d3dcompiler_47.dll
- libEGL.dll
- libGLESv2.dll
下方文件包含CEF,Chromium和Blink使用的非本地化资源。没有这些文件,任何Web组件可能会显示不正确。
- resources.pak
- chrome_100_percent.pak
- chrome_200_percent.pak