libcef
文章目录
- 1.介绍
- 2.JS执行
- 3.窗口绑定
- 4.扩展
- 5.JS的基本类型
- 5.1.JS数组
- 5.2.JS对象
- 5.3.具有访问器的对象
- 5.4.JS函数
- 6.函数和窗口绑定
- 7.功能和扩展
- 8.上下文的使用
- 9.执行函数
- 9.1.使用JS回调
- 10.抛出异常
1.介绍
Chromium和CEF使用V8 JavaScript引擎来实现内部JavaScript (JS)。浏览器中的每个框架都有自己的JS上下文,为在该框架中执行的JS代码提供作用域和安全性(更多信息请参阅“使用上下文”部分)。CEF为客户端应用程序的集成提供了大量的JS特性。
在CEF3中,Blink (WebKit)和JS的执行在一个单独的渲染程序中运行。渲染器进程中的主线程被标识为TID_RENDERER,所有V8的执行都必须在这个线程上进行。与JS执行相关的回调是通过CefRenderProcessHandler接口公开的。当一个新的渲染器进程初始化时,通过CefApp::GetRenderProcessHandler()获取该接口。
在浏览器和呈现程序之间通信的JS api应该使用异步回调来设计。有关更多信息,请参阅GeneralUsage wiki页面的“异步JavaScript绑定”部分。
2.JS执行
从客户端应用程序执行JS的最简单方法是使用CefFrame::ExecuteJavaScript()函数。这个函数在浏览器进程和渲染器进程中都可用,并且可以在JS上下文之外安全地使用。
CefRefPtr<CefBrowser> browser = ...;
CefRefPtr<CefFrame> frame = browser->GetMainFrame();
frame->ExecuteJavaScript("alert('ExecuteJavaScript works!');",frame->GetURL(), 0);
上面的例子会引起alert(‘ExecuteJavaScript works!’);在浏览器的主框架中执行。ExecuteJavaScript()函数可用于与框架JS上下文中的函数和变量交互。为了从JS返回值到客户端应用程序,可以考虑使用窗口绑定或扩展。
3.窗口绑定
窗口绑定允许客户端应用程序将值附加到框架的窗口对象。窗口绑定是使用CefRenderProcessHandler::OnContextCreated()方法实现的。
void MyRenderProcessHandler::OnContextCreated(CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame> frame,CefRefPtr<CefV8Context> context) {// Retrieve the context's window object.CefRefPtr<CefV8Value> object = context->GetGlobal();// Create a new V8 string value. See the "Basic JS Types" section below.CefRefPtr<CefV8Value> str = CefV8Value::CreateString("My Value!");// Add the string to the window object as "window.myval". See the "JS Objects" section below.object->SetValue("myval", str, V8_PROPERTY_ATTRIBUTE_NONE);
}
然后,框架中的JavaScript可以与窗口绑定交互。
<script language="JavaScript">
alert(window.myval); // Shows an alert box with "My Value!"
</script>
每当一个帧被重新加载时,窗口绑定都会被重新加载,这样客户端应用程序就有机会在必要时更改绑定。例如,不同的帧可以通过修改绑定到该帧的窗口对象的值来访问客户端应用程序中的不同特性。
4.扩展
扩展类似于窗口绑定,只不过它们被加载到每一帧的上下文中,一旦加载就不能修改。加载扩展时,DOM不存在,在扩展加载期间试图访问DOM将导致崩溃。扩展是使用CefRegisterExtension()函数注册的,该函数应该从CefRenderProcessHandler::OnWebKitInitialized()方法调用。
void MyRenderProcessHandler::OnWebKitInitialized() {// Define the extension contents.std::string extensionCode ="var test;""if (!test)"" test = {};""(function() {"" test.myval = 'My Value!';""})();";// Register the extension.CefRegisterExtension("v8/test", extensionCode, NULL);
}
extensionCode表示的字符串可以是任何有效的JS代码。然后,框架中的JS可以与扩展代码交互。
<script language="JavaScript">
alert(test.myval); // Shows an alert box with "My Value!"
</script>
5.JS的基本类型
CEF支持基本JS数据类型的创建,包括undefined, null, bool, int, double, date和string。这些类型是使用CefV8Value::Create*()静态方法创建的。例如,要创建一个新的JS字符串值,使用CreateString()方法。
CefRefPtr<CefV8Value> str = CefV8Value::CreateString("My Value!");
基本值类型可以在任何时候创建,并且最初并不与特定的上下文相关联(有关更多信息,请参阅“使用上下文”部分)。要测试值类型,请使用Is*()方法。
CefRefPtr<CefV8Value> val = ...;
if (val.IsString()) {// The value is a string.
}
要检索值,请使用Get* value()方法。
CefString strVal = val.GetStringValue();
5.1.JS数组
数组是使用CefV8Value::CreateArray()静态方法创建的,该方法接受一个长度参数。数组只能在上下文中创建和使用(有关更多信息,请参阅“使用上下文”一节)。
// Create an array that can contain two values.
CefRefPtr<CefV8Value> arr = CefV8Value::CreateArray(2);
使用SetValue()方法变量将值赋给数组,该方法变量以索引作为第一个参数。
// Add two values to the array.
arr->SetValue(0, CefV8Value::CreateString("My First String!"));
arr->SetValue(1, CefV8Value::CreateString("My Second String!"));
要测试CefV8Value是否是数组,请使用IsArray()方法。要获取数组的长度,请使用GetArrayLength()方法。要从数组中获取值,请使用GetValue()变量,该变量以索引作为第一个参数。
5.2.JS对象
对象是使用CefV8Value::CreateObject()静态方法创建的,该方法接受一个可选的CefV8Accessor参数。对象只能在上下文中创建和使用(有关更多信息,请参阅“使用上下文”部分)。
CefRefPtr<CefV8Value> obj = CefV8Value::CreateObject(NULL);
使用SetValue()方法变量将值赋给对象,该方法变量以键字符串作为第一个参数。
obj->SetValue("myval", CefV8Value::CreateString("My String!"));
5.3.具有访问器的对象
对象可以有一个相关联的CefV8Accessor,该CefV8Accessor提供了获取和设置值的本机实现。
CefRefPtr<CefV8Accessor> accessor = …;
CefRefPtr<CefV8Value> obj = CefV8Value::CreateObject(accessor);
客户端应用程序必须提供的CefV8Accessor接口的实现。
class MyV8Accessor : public CefV8Accessor {
public:MyV8Accessor() {}virtual bool Get(const CefString& name,const CefRefPtr<CefV8Value> object,CefRefPtr<CefV8Value>& retval,CefString& exception) OVERRIDE {if (name == "myval") {// Return the value.retval = CefV8Value::CreateString(myval_);return true;}// Value does not exist.return false;}virtual bool Set(const CefString& name,const CefRefPtr<CefV8Value> object,const CefRefPtr<CefV8Value> value,CefString& exception) OVERRIDE {if (name == "myval") {if (value->IsString()) {// Store the value.myval_ = value->GetStringValue();} else {// Throw an exception.exception = "Invalid value type";}return true;}// Value does not exist.return false;}// Variable used for storing the value.CefString myval_;// Provide the reference counting implementation for this class.IMPLEMENT_REFCOUNTING(MyV8Accessor);
};
为了将值传递给访问器,必须使用接受AccessControl和PropertyAttribute参数的SetValue()方法变体进行设置。
obj->SetValue("myval", V8_ACCESS_CONTROL_DEFAULT, V8_PROPERTY_ATTRIBUTE_NONE);
5.4.JS函数
CEF支持使用本地实现创建JS函数。函数是使用CefV8Value::CreateFunction()静态方法创建的,该方法接受name和CefV8Handler参数。函数只能在上下文中创建和使用(有关更多信息,请参阅“使用上下文”部分)。
CefRefPtr<CefV8Handler> handler = …;
CefRefPtr<CefV8Value> func = CefV8Value::CreateFunction("myfunc", handler);
必须由客户机应用程序提供的CefV8Handler接口的实现。
class MyV8Handler : public CefV8Handler {
public:MyV8Handler() {}virtual bool Execute(const CefString& name,CefRefPtr<CefV8Value> object,const CefV8ValueList& arguments,CefRefPtr<CefV8Value>& retval,CefString& exception) OVERRIDE {if (name == "myfunc") {// Return my string value.retval = CefV8Value::CreateString("My Value!");return true;}// Function does not exist.return false;}// Provide the reference counting implementation for this class.IMPLEMENT_REFCOUNTING(MyV8Handler);
};
6.函数和窗口绑定
函数可用于创建复杂的窗口绑定。
void MyRenderProcessHandler::OnContextCreated(CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame> frame,CefRefPtr<CefV8Context> context) {// Retrieve the context's window object.CefRefPtr<CefV8Value> object = context->GetGlobal();// Create an instance of my CefV8Handler object.CefRefPtr<CefV8Handler> handler = new MyV8Handler();// Create the "myfunc" function.CefRefPtr<CefV8Value> func = CefV8Value::CreateFunction("myfunc", handler);// Add the "myfunc" function to the "window" object.object->SetValue("myfunc", func, V8_PROPERTY_ATTRIBUTE_NONE);
}
<script language="JavaScript">
alert(window.myfunc()); // Shows an alert box with "My Value!"
</script>
7.功能和扩展
函数可以用来创建复杂的扩展。注意使用“本机函数”前向声明,这在使用扩展时是必需的。
void MyRenderProcessHandler::OnWebKitInitialized() {// Define the extension contents.std::string extensionCode ="var test;""if (!test)"" test = {};""(function() {"" test.myfunc = function() {"" native function myfunc();"" return myfunc();"" };""})();";// Create an instance of my CefV8Handler object.CefRefPtr<CefV8Handler> handler = new MyV8Handler();// Register the extension.CefRegisterExtension("v8/test", extensionCode, handler);
}
<script language="JavaScript">
alert(test.myfunc()); // Shows an alert box with "My Value!"
</script>
8.上下文的使用
浏览器窗口中的每一帧都有自己的V8上下文。上下文定义了在该框架中定义的所有变量、对象和函数的作用域。如果当前代码位置在调用堆栈的更高位置有CefV8Handler、CefV8Accessor或OnContextCreated()/ oncontexttremonitored()回调,V8将位于上下文中。
OnContextCreated()和oncontexttreleasing()方法定义了与一个框架相关的V8上下文的完整生命周期。在使用这些方法时,请注意遵循以下规则:
不要在调用V8上下文的oncontextreleasing()之后保留或使用V8上下文引用。
所有V8对象的生命周期都是未指定的(直到GC)。在维护直接从V8对象到自己内部实现对象的引用时要小心。在很多情况下,使用你的应用程序与V8上下文关联的代理对象可能会更好,当上下文调用oncontextreleasing()时,它可以被“断开连接”(允许你的内部实现对象被释放)。
如果V8当前不在上下文中,或者需要检索和存储对上下文的引用,可以使用两个可用的CefV8Context静态方法之一。GetCurrentContext()返回当前正在执行JS的帧的上下文。GetEnteredContext()返回JS开始执行的帧的上下文。例如,如果frame1中的一个函数调用了frame2中的一个函数,那么当前上下文将是frame2,而输入的上下文将是frame1。
数组、对象和函数只能被创建、修改,如果V8是在上下文中,对于函数,则只能被执行。如果V8不在上下文中,那么应用程序需要通过调用enter()进入上下文,并通过调用exit()退出上下文。Enter()和Exit()方法只能使用:
在现有上下文之外创建V8对象、函数或数组时。例如,当创建一个JS对象以响应本地菜单回调时。
在当前上下文以外的上下文中创建V8对象、函数或数组时。例如,如果来自frame1的调用需要修改frame2的上下文。
9.执行函数
本机代码可以通过使用ExecuteFunction()和ExecuteFunctionWithContext()方法来执行JS函数。ExecuteFunction()方法只能在V8已经在上下文中使用,如“使用上下文”部分所述。ExecuteFunctionWithContext()方法允许应用程序指定将要输入执行的上下文。
9.1.使用JS回调
当用本地代码注册JS函数回调时,应用程序应该在本地代码中存储对当前上下文和JS函数的引用。这可以按如下方式实现。
- 在OnJSBinding()中创建一个“register”函数。
void MyRenderProcessHandler::OnContextCreated(CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame> frame,CefRefPtr<CefV8Context> context) {// Retrieve the context's window object.CefRefPtr<CefV8Value> object = context->GetGlobal();CefRefPtr<CefV8Handler> handler = new MyV8Handler(this);object->SetValue("register",CefV8Value::CreateFunction("register", handler),V8_PROPERTY_ATTRIBUTE_NONE);
}
- 在“register”函数的MyV8Handler::Execute()实现中,保持对上下文和函数的引用。
bool MyV8Handler::Execute(const CefString& name,CefRefPtr<CefV8Value> object,const CefV8ValueList& arguments,CefRefPtr<CefV8Value>& retval,CefString& exception) {if (name == "register") {if (arguments.size() == 1 && arguments[0]->IsFunction()) {callback_func_ = arguments[0];callback_context_ = CefV8Context::GetCurrentContext();return true;}}return false;
}
- 通过JavaScript注册JS回调。
<script language="JavaScript">
function myFunc() {// do something in JS.
}
window.register(myFunc);
</script>
- 在以后的某个时间执行JS回调。
CefV8ValueList args;
CefRefPtr<CefV8Value> retval;
CefRefPtr<CefV8Exception> exception;
if (callback_func_->ExecuteFunctionWithContext(callback_context_, NULL, args, retval, exception, false)) {if (exception.get()) {// Execution threw an exception.} else {// Execution succeeded.}
}
有关使用回调的更多信息,请参阅GeneralUsage wiki页面的异步JavaScript绑定部分。
10.抛出异常
如果在CefV8Value::ExecuteFunction*()之前调用CefV8Value::SetRethrowExceptions(true),则V8在函数执行期间生成的任何异常都将立即被重新抛出。如果一个异常被重新抛出,任何本地代码都需要立即返回。只有当在调用堆栈中有一个更高的JS调用时,才应该重新抛出异常。例如,考虑以下调用堆栈,其中“JS”是一个JS函数,“EF”是一个本地ExecuteFunction调用:
堆栈1:JS1 ->EF1→JS2→EF2
堆栈2:本地菜单->EF1→JS2→EF2
在堆栈1中,EF1和EF2的重抛出都应该为真。对于堆栈2,重抛出EF1为假,EF2为真。
这可以通过在本机代码中有两种EF调用站点来实现:
只能从V8处理器调用。这包括堆栈1中的ef1和EF2以及堆栈2中的EF2。重新投掷总是正确的。
只在本地调用。这包括堆栈2中的EF1。重新投掷总是假的。
在重新抛出异常时要非常小心。不正确的使用(例如,在异常被重新抛出后立即调用ExecuteFunction())可能会导致应用程序崩溃或以难以调试的方式出现故障。
libcef
文章目录
- 1.介绍
- 2.JS执行
- 3.窗口绑定
- 4.扩展
- 5.JS的基本类型
- 5.1.JS数组
- 5.2.JS对象
- 5.3.具有访问器的对象
- 5.4.JS函数
- 6.函数和窗口绑定
- 7.功能和扩展
- 8.上下文的使用
- 9.执行函数
- 9.1.使用JS回调
- 10.抛出异常
1.介绍
Chromium和CEF使用V8 JavaScript引擎来实现内部JavaScript (JS)。浏览器中的每个框架都有自己的JS上下文,为在该框架中执行的JS代码提供作用域和安全性(更多信息请参阅“使用上下文”部分)。CEF为客户端应用程序的集成提供了大量的JS特性。
在CEF3中,Blink (WebKit)和JS的执行在一个单独的渲染程序中运行。渲染器进程中的主线程被标识为TID_RENDERER,所有V8的执行都必须在这个线程上进行。与JS执行相关的回调是通过CefRenderProcessHandler接口公开的。当一个新的渲染器进程初始化时,通过CefApp::GetRenderProcessHandler()获取该接口。
在浏览器和呈现程序之间通信的JS api应该使用异步回调来设计。有关更多信息,请参阅GeneralUsage wiki页面的“异步JavaScript绑定”部分。
2.JS执行
从客户端应用程序执行JS的最简单方法是使用CefFrame::ExecuteJavaScript()函数。这个函数在浏览器进程和渲染器进程中都可用,并且可以在JS上下文之外安全地使用。
CefRefPtr<CefBrowser> browser = ...;
CefRefPtr<CefFrame> frame = browser->GetMainFrame();
frame->ExecuteJavaScript("alert('ExecuteJavaScript works!');",frame->GetURL(), 0);
上面的例子会引起alert(‘ExecuteJavaScript works!’);在浏览器的主框架中执行。ExecuteJavaScript()函数可用于与框架JS上下文中的函数和变量交互。为了从JS返回值到客户端应用程序,可以考虑使用窗口绑定或扩展。
3.窗口绑定
窗口绑定允许客户端应用程序将值附加到框架的窗口对象。窗口绑定是使用CefRenderProcessHandler::OnContextCreated()方法实现的。
void MyRenderProcessHandler::OnContextCreated(CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame> frame,CefRefPtr<CefV8Context> context) {// Retrieve the context's window object.CefRefPtr<CefV8Value> object = context->GetGlobal();// Create a new V8 string value. See the "Basic JS Types" section below.CefRefPtr<CefV8Value> str = CefV8Value::CreateString("My Value!");// Add the string to the window object as "window.myval". See the "JS Objects" section below.object->SetValue("myval", str, V8_PROPERTY_ATTRIBUTE_NONE);
}
然后,框架中的JavaScript可以与窗口绑定交互。
<script language="JavaScript">
alert(window.myval); // Shows an alert box with "My Value!"
</script>
每当一个帧被重新加载时,窗口绑定都会被重新加载,这样客户端应用程序就有机会在必要时更改绑定。例如,不同的帧可以通过修改绑定到该帧的窗口对象的值来访问客户端应用程序中的不同特性。
4.扩展
扩展类似于窗口绑定,只不过它们被加载到每一帧的上下文中,一旦加载就不能修改。加载扩展时,DOM不存在,在扩展加载期间试图访问DOM将导致崩溃。扩展是使用CefRegisterExtension()函数注册的,该函数应该从CefRenderProcessHandler::OnWebKitInitialized()方法调用。
void MyRenderProcessHandler::OnWebKitInitialized() {// Define the extension contents.std::string extensionCode ="var test;""if (!test)"" test = {};""(function() {"" test.myval = 'My Value!';""})();";// Register the extension.CefRegisterExtension("v8/test", extensionCode, NULL);
}
extensionCode表示的字符串可以是任何有效的JS代码。然后,框架中的JS可以与扩展代码交互。
<script language="JavaScript">
alert(test.myval); // Shows an alert box with "My Value!"
</script>
5.JS的基本类型
CEF支持基本JS数据类型的创建,包括undefined, null, bool, int, double, date和string。这些类型是使用CefV8Value::Create*()静态方法创建的。例如,要创建一个新的JS字符串值,使用CreateString()方法。
CefRefPtr<CefV8Value> str = CefV8Value::CreateString("My Value!");
基本值类型可以在任何时候创建,并且最初并不与特定的上下文相关联(有关更多信息,请参阅“使用上下文”部分)。要测试值类型,请使用Is*()方法。
CefRefPtr<CefV8Value> val = ...;
if (val.IsString()) {// The value is a string.
}
要检索值,请使用Get* value()方法。
CefString strVal = val.GetStringValue();
5.1.JS数组
数组是使用CefV8Value::CreateArray()静态方法创建的,该方法接受一个长度参数。数组只能在上下文中创建和使用(有关更多信息,请参阅“使用上下文”一节)。
// Create an array that can contain two values.
CefRefPtr<CefV8Value> arr = CefV8Value::CreateArray(2);
使用SetValue()方法变量将值赋给数组,该方法变量以索引作为第一个参数。
// Add two values to the array.
arr->SetValue(0, CefV8Value::CreateString("My First String!"));
arr->SetValue(1, CefV8Value::CreateString("My Second String!"));
要测试CefV8Value是否是数组,请使用IsArray()方法。要获取数组的长度,请使用GetArrayLength()方法。要从数组中获取值,请使用GetValue()变量,该变量以索引作为第一个参数。
5.2.JS对象
对象是使用CefV8Value::CreateObject()静态方法创建的,该方法接受一个可选的CefV8Accessor参数。对象只能在上下文中创建和使用(有关更多信息,请参阅“使用上下文”部分)。
CefRefPtr<CefV8Value> obj = CefV8Value::CreateObject(NULL);
使用SetValue()方法变量将值赋给对象,该方法变量以键字符串作为第一个参数。
obj->SetValue("myval", CefV8Value::CreateString("My String!"));
5.3.具有访问器的对象
对象可以有一个相关联的CefV8Accessor,该CefV8Accessor提供了获取和设置值的本机实现。
CefRefPtr<CefV8Accessor> accessor = …;
CefRefPtr<CefV8Value> obj = CefV8Value::CreateObject(accessor);
客户端应用程序必须提供的CefV8Accessor接口的实现。
class MyV8Accessor : public CefV8Accessor {
public:MyV8Accessor() {}virtual bool Get(const CefString& name,const CefRefPtr<CefV8Value> object,CefRefPtr<CefV8Value>& retval,CefString& exception) OVERRIDE {if (name == "myval") {// Return the value.retval = CefV8Value::CreateString(myval_);return true;}// Value does not exist.return false;}virtual bool Set(const CefString& name,const CefRefPtr<CefV8Value> object,const CefRefPtr<CefV8Value> value,CefString& exception) OVERRIDE {if (name == "myval") {if (value->IsString()) {// Store the value.myval_ = value->GetStringValue();} else {// Throw an exception.exception = "Invalid value type";}return true;}// Value does not exist.return false;}// Variable used for storing the value.CefString myval_;// Provide the reference counting implementation for this class.IMPLEMENT_REFCOUNTING(MyV8Accessor);
};
为了将值传递给访问器,必须使用接受AccessControl和PropertyAttribute参数的SetValue()方法变体进行设置。
obj->SetValue("myval", V8_ACCESS_CONTROL_DEFAULT, V8_PROPERTY_ATTRIBUTE_NONE);
5.4.JS函数
CEF支持使用本地实现创建JS函数。函数是使用CefV8Value::CreateFunction()静态方法创建的,该方法接受name和CefV8Handler参数。函数只能在上下文中创建和使用(有关更多信息,请参阅“使用上下文”部分)。
CefRefPtr<CefV8Handler> handler = …;
CefRefPtr<CefV8Value> func = CefV8Value::CreateFunction("myfunc", handler);
必须由客户机应用程序提供的CefV8Handler接口的实现。
class MyV8Handler : public CefV8Handler {
public:MyV8Handler() {}virtual bool Execute(const CefString& name,CefRefPtr<CefV8Value> object,const CefV8ValueList& arguments,CefRefPtr<CefV8Value>& retval,CefString& exception) OVERRIDE {if (name == "myfunc") {// Return my string value.retval = CefV8Value::CreateString("My Value!");return true;}// Function does not exist.return false;}// Provide the reference counting implementation for this class.IMPLEMENT_REFCOUNTING(MyV8Handler);
};
6.函数和窗口绑定
函数可用于创建复杂的窗口绑定。
void MyRenderProcessHandler::OnContextCreated(CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame> frame,CefRefPtr<CefV8Context> context) {// Retrieve the context's window object.CefRefPtr<CefV8Value> object = context->GetGlobal();// Create an instance of my CefV8Handler object.CefRefPtr<CefV8Handler> handler = new MyV8Handler();// Create the "myfunc" function.CefRefPtr<CefV8Value> func = CefV8Value::CreateFunction("myfunc", handler);// Add the "myfunc" function to the "window" object.object->SetValue("myfunc", func, V8_PROPERTY_ATTRIBUTE_NONE);
}
<script language="JavaScript">
alert(window.myfunc()); // Shows an alert box with "My Value!"
</script>
7.功能和扩展
函数可以用来创建复杂的扩展。注意使用“本机函数”前向声明,这在使用扩展时是必需的。
void MyRenderProcessHandler::OnWebKitInitialized() {// Define the extension contents.std::string extensionCode ="var test;""if (!test)"" test = {};""(function() {"" test.myfunc = function() {"" native function myfunc();"" return myfunc();"" };""})();";// Create an instance of my CefV8Handler object.CefRefPtr<CefV8Handler> handler = new MyV8Handler();// Register the extension.CefRegisterExtension("v8/test", extensionCode, handler);
}
<script language="JavaScript">
alert(test.myfunc()); // Shows an alert box with "My Value!"
</script>
8.上下文的使用
浏览器窗口中的每一帧都有自己的V8上下文。上下文定义了在该框架中定义的所有变量、对象和函数的作用域。如果当前代码位置在调用堆栈的更高位置有CefV8Handler、CefV8Accessor或OnContextCreated()/ oncontexttremonitored()回调,V8将位于上下文中。
OnContextCreated()和oncontexttreleasing()方法定义了与一个框架相关的V8上下文的完整生命周期。在使用这些方法时,请注意遵循以下规则:
不要在调用V8上下文的oncontextreleasing()之后保留或使用V8上下文引用。
所有V8对象的生命周期都是未指定的(直到GC)。在维护直接从V8对象到自己内部实现对象的引用时要小心。在很多情况下,使用你的应用程序与V8上下文关联的代理对象可能会更好,当上下文调用oncontextreleasing()时,它可以被“断开连接”(允许你的内部实现对象被释放)。
如果V8当前不在上下文中,或者需要检索和存储对上下文的引用,可以使用两个可用的CefV8Context静态方法之一。GetCurrentContext()返回当前正在执行JS的帧的上下文。GetEnteredContext()返回JS开始执行的帧的上下文。例如,如果frame1中的一个函数调用了frame2中的一个函数,那么当前上下文将是frame2,而输入的上下文将是frame1。
数组、对象和函数只能被创建、修改,如果V8是在上下文中,对于函数,则只能被执行。如果V8不在上下文中,那么应用程序需要通过调用enter()进入上下文,并通过调用exit()退出上下文。Enter()和Exit()方法只能使用:
在现有上下文之外创建V8对象、函数或数组时。例如,当创建一个JS对象以响应本地菜单回调时。
在当前上下文以外的上下文中创建V8对象、函数或数组时。例如,如果来自frame1的调用需要修改frame2的上下文。
9.执行函数
本机代码可以通过使用ExecuteFunction()和ExecuteFunctionWithContext()方法来执行JS函数。ExecuteFunction()方法只能在V8已经在上下文中使用,如“使用上下文”部分所述。ExecuteFunctionWithContext()方法允许应用程序指定将要输入执行的上下文。
9.1.使用JS回调
当用本地代码注册JS函数回调时,应用程序应该在本地代码中存储对当前上下文和JS函数的引用。这可以按如下方式实现。
- 在OnJSBinding()中创建一个“register”函数。
void MyRenderProcessHandler::OnContextCreated(CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame> frame,CefRefPtr<CefV8Context> context) {// Retrieve the context's window object.CefRefPtr<CefV8Value> object = context->GetGlobal();CefRefPtr<CefV8Handler> handler = new MyV8Handler(this);object->SetValue("register",CefV8Value::CreateFunction("register", handler),V8_PROPERTY_ATTRIBUTE_NONE);
}
- 在“register”函数的MyV8Handler::Execute()实现中,保持对上下文和函数的引用。
bool MyV8Handler::Execute(const CefString& name,CefRefPtr<CefV8Value> object,const CefV8ValueList& arguments,CefRefPtr<CefV8Value>& retval,CefString& exception) {if (name == "register") {if (arguments.size() == 1 && arguments[0]->IsFunction()) {callback_func_ = arguments[0];callback_context_ = CefV8Context::GetCurrentContext();return true;}}return false;
}
- 通过JavaScript注册JS回调。
<script language="JavaScript">
function myFunc() {// do something in JS.
}
window.register(myFunc);
</script>
- 在以后的某个时间执行JS回调。
CefV8ValueList args;
CefRefPtr<CefV8Value> retval;
CefRefPtr<CefV8Exception> exception;
if (callback_func_->ExecuteFunctionWithContext(callback_context_, NULL, args, retval, exception, false)) {if (exception.get()) {// Execution threw an exception.} else {// Execution succeeded.}
}
有关使用回调的更多信息,请参阅GeneralUsage wiki页面的异步JavaScript绑定部分。
10.抛出异常
如果在CefV8Value::ExecuteFunction*()之前调用CefV8Value::SetRethrowExceptions(true),则V8在函数执行期间生成的任何异常都将立即被重新抛出。如果一个异常被重新抛出,任何本地代码都需要立即返回。只有当在调用堆栈中有一个更高的JS调用时,才应该重新抛出异常。例如,考虑以下调用堆栈,其中“JS”是一个JS函数,“EF”是一个本地ExecuteFunction调用:
堆栈1:JS1 ->EF1→JS2→EF2
堆栈2:本地菜单->EF1→JS2→EF2
在堆栈1中,EF1和EF2的重抛出都应该为真。对于堆栈2,重抛出EF1为假,EF2为真。
这可以通过在本机代码中有两种EF调用站点来实现:
只能从V8处理器调用。这包括堆栈1中的ef1和EF2以及堆栈2中的EF2。重新投掷总是正确的。
只在本地调用。这包括堆栈2中的EF1。重新投掷总是假的。
在重新抛出异常时要非常小心。不正确的使用(例如,在异常被重新抛出后立即调用ExecuteFunction())可能会导致应用程序崩溃或以难以调试的方式出现故障。