Chromium和CEF将V8 JavaScript引擎用于其内部JavaScript(JS)实现。浏览器中的每个框架都有其自己的JS上下文,该上下文为在该框架中执行的JS代码提供范围和安全性(有关更多信息,请参见“使用上下文”部分)。CEF公开了许多JS功能以集成到客户端应用程序中。
使用CEF3 Blink(WebKit)(Blink:闪烁)和JS执行可在单独的渲染器进程中运行。渲染器进程中的主线程被标识为TID_RENDERER,并且所有V8执行都必须在此线程上进行。与JS执行相关的回调通过CefRenderProcessHandler接口公开。初始化新的渲染器进程时,可通过CefApp :: GetRenderProcessHandler()检索此接口。
在浏览器和渲染器进程之间通信的JS API应该使用异步回调进行设计。有关更多信息,请参见GeneralUsage Wiki页面的“异步JavaScript绑定”部分。
ExecuteJavaScript从客户端应用程序执行JS的最简单方法是使用CefFrame :: ExecuteJavaScript()函数。此功能在浏览器进程和渲染器进程中均可用,并且可以从JS上下文外部安全使用。
CefRefPtr browser = ...;
CefRefPtr frame = browser->GetMainFrame();
frame->ExecuteJavaScript("alert('ExecuteJavaScript works!');", frame->GetURL(), 0);
上面的示例将导致alert('ExecuteJavaScript works!');
在浏览器的主框架中执行。
ExecuteJavaScript()函数可用于与框架的JS上下文中的函数和变量进行交互。为了将值从JS返回到客户端应用程序,请考虑使用“窗口绑定”或“扩展”。
窗口绑定窗口绑定允许客户端应用程序将值附加到框架的window
对象。窗口绑定是使用CefRenderProcessHandler :: OnContextCreated()方法实现的。
void MyRenderProcessHandler::OnContextCreated(
CefRefPtr browser,
CefRefPtr frame,
CefRefPtr context) {
// Retrieve the context's window object.
CefRefPtr object = context->GetGlobal();
// Create a new V8 string value. See the "Basic JS Types" section below.
CefRefPtr 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可以与窗口绑定进行交互。
alert(window.myval); // Shows an alert box with "My Value!"
每次重新加载框架时,窗口绑定都会重新加载,从而使客户端应用程序有必要在必要时更改绑定。例如,通过修改绑定到该框架的窗口对象的值,可以为不同的框架提供访问客户端应用程序中不同功能的权限。
扩展类似于窗口绑定,只是扩展被加载到每一个iframe的上下文中,并且一旦加载就无法修改。加载扩展时,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然后可以与扩展代码进行交互。
alert(test.myval); // Shows an alert box with "My Value!"
基本JS类型
CEF支持创建基本的JS数据类型,包括undefined,null,bool,int,double,date和string。这些类型是使用CefV8Value :: Create * ()静态方法创建的。例如,要创建新的JS字符串值,请使用CreateString()方法。
CefRefPtr str = CefV8Value::CreateString("My Value!");
基本值类型可以随时创建,并且最初不与特定上下文关联(有关更多信息,请参见“使用上下文”部分)。
要测试值类型,可使用Is * ()方法。
CefRefPtr val = ...;
if (val.IsString()) {
// The value is a string.
}
要获取值,请使用Get * Value()方法。
CefString strVal = val.GetStringValue();
JS数组
数组是使用CefV8Value :: CreateArray()静态方法创建的,该方法接受一个length参数。只能在上下文中创建和使用数组(有关更多信息,请参见“使用上下文”部分)。
// Create an array that can contain two values.
CefRefPtr 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()变体,该变体将索引作为第一个参数。
JS对象使用CefV8Value :: CreateObject()静态方法创建对象,该方法采用可选的CefV8Accessor参数。只能在上下文中创建和使用对象(有关更多信息,请参见“使用上下文”部分)。
CefRefPtr obj = CefV8Value::CreateObject(NULL);
使用SetValue()方法将值分配给对象,该方法变量将键字符串作为第一个参数。
obj->SetValue("myval", CefV8Value::CreateString("My String!"));
带访问器的对象
对象可以选择具有关联的CefV8Accessor,该CefV8Accessor提供用于获取和设置值的本机实现。
CefRefPtr accessor = …;
CefRefPtr obj = CefV8Value::CreateObject(accessor);
客户端应用程序必须提供的CefV8Accessor接口的实现。
class MyV8Accessor : public CefV8Accessor {
public:
MyV8Accessor() {}
virtual bool Get(const CefString& name,
const CefRefPtr object,
CefRefPtr& 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 object,
const CefRefPtr 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);
JS函数
CEF支持使用本机实现创建JS函数。使用CefV8Value :: CreateFunction()静态方法创建函数,该方法接受name和CefV8Handler参数。函数只能在上下文中创建和使用(有关更多信息,请参见“使用上下文”部分)。
CefRefPtr handler = …;
CefRefPtr func = CefV8Value::CreateFunction("myfunc", handler);
客户端应用程序必须提供的CefV8Handler接口的实现。
class MyV8Handler : public CefV8Handler {
public:
MyV8Handler() {}
virtual bool Execute(const CefString& name,
CefRefPtr object,
const CefV8ValueList& arguments,
CefRefPtr& 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);
};
函数和window对象绑定
函数可用于创建复杂的窗口绑定。
void MyRenderProcessHandler::OnContextCreated(
CefRefPtr browser,
CefRefPtr frame,
CefRefPtr context) {
// Retrieve the context's window object.
CefRefPtr object = context->GetGlobal();
// Create an instance of my CefV8Handler object.
CefRefPtr handler = new MyV8Handler();
// Create the "myfunc" function.
CefRefPtr func = CefV8Value::CreateFunction("myfunc", handler);
// Add the "myfunc" function to the "window" object.
object->SetValue("myfunc", func, V8_PROPERTY_ATTRIBUTE_NONE);
}
alert(window.myfunc()); // Shows an alert box with "My Value!"
功能和扩展
函数可用于创建复杂的扩展。请注意,使用扩展时需要使用“本机函数”前向声明。
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 handler = new MyV8Handler();
// Register the extension.
CefRegisterExtension("v8/test", extensionCode, handler);
}
alert(test.myfunc()); // Shows an alert box with "My Value!"
使用上下文
浏览器窗口中的每个框架都有其自己的V8上下文。上下文定义了该框架中定义的所有变量,对象和函数的范围。如果当前代码位置在调用堆栈中具有较高的CefV8Handler,CefV8Accessor或OnContextCreated()/ OnContextReleased()回调,则V8将位于上下文内。
OnContextCreated()和OnContextReleased()方法定义与框架关联的V8上下文的完整寿命。使用这些方法时,请务必遵循以下规则:
-
在对该上下文的OnContextReleased()调用之后,请勿保留或使用V8上下文引用。
-
未指定所有V8对象的寿命(直到GC)。直接维护从V8对象到您自己的内部实现对象的引用时要小心。在许多情况下,最好使用一个代理对象,该应用程序将您的应用程序与V8上下文相关联,并且在为上下文调用OnContextReleased()时可以“断开连接”(允许释放内部实现对象)。
如果V8当前不在上下文中,或者如果您需要检索和存储对上下文的引用,则可以使用两个可用的CefV8Context静态方法之一。GetCurrentContext()返回当前正在执行JS的框架的上下文。GetEnteredContext()返回JS执行开始的框架的上下文。例如,如果frame1中的函数调用frame2中的函数,则当前上下文将为frame2,而输入的上下文将为frame1。
如果V8在上下文中,则只能创建,修改和执行数组,对象和函数,对于函数而言,只能执行。如果V8不在上下文中,则应用程序需要通过调用Enter()进入上下文,并通过调用Exit()退出上下文。仅应使用Enter()和Exit()方法:
-
在现有上下文之外创建V8对象,函数或数组时。例如,在创建JS对象以响应本机菜单回调时。
-
在当前上下文以外的上下文中创建V8对象,函数或数组时。例如,如果源自frame1的呼叫需要修改frame2的上下文。
本机代码可以使用ExecuteFunction()和ExecuteFunctionWithContext()方法执行JS函数。仅当V8已在上下文中(如“使用上下文”一节中所述)时,才应使用ExecuteFunction()方法。ExecuteFunctionWithContext()方法允许应用程序指定要输入的执行上下文。
使用JS回调在使用本机代码注册JS函数回调时,应用程序应在本机代码中存储对当前上下文和JS函数的引用。这可以如下实现。
1.在OnJSBinding()中创建一个“注册”函数。
void MyRenderProcessHandler::OnContextCreated(
CefRefPtr browser,
CefRefPtr frame,
CefRefPtr context) {
// Retrieve the context's window object.
CefRefPtr object = context->GetGlobal();
CefRefPtr handler = new MyV8Handler(this);
object->SetValue("register",
CefV8Value::CreateFunction("register", handler),
V8_PROPERTY_ATTRIBUTE_NONE);
}
2.在MyV8Handler :: Execute()实现中,“注册”函数保留对上下文和函数的引用。
bool MyV8Handler::Execute(const CefString& name,
CefRefPtr object,
const CefV8ValueList& arguments,
CefRefPtr& 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;
}
3.通过JavaScript注册JS回调。
function myFunc() {
// do something in JS.
}
window.register(myFunc);
4.稍后执行JS回调。
CefV8ValueList args;
CefRefPtr retval;
CefRefPtr 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绑定”部分。
抛出异常如果在CefV8Value :: ExecuteFunction * ()之前调用CefV8Value :: SetRethrowExceptions(true),则V8在函数执行期间生成的任何异常都将立即被抛出。如果引发异常,则任何本机代码都需要立即返回。只有在调用堆栈中有一个更高的JS调用时,才应重新抛出异常。例如,考虑以下调用堆栈,其中“ JS”是JS函数,“ EF”是本机ExecuteFunction调用:
堆栈1:JS1-> EF1-> JS2-> EF2
堆栈2:本机菜单-> EF1-> JS2-> EF2
对于堆栈1,EF1和EF2的重新抛出均应为true。对于堆栈2,重新抛出对于EF1应该为false,对于EF2应该为true。
这可以通过在本机代码中为EF设置两个呼叫站点来实现:
-
仅从V8处理程序调用。这涵盖了堆栈1中的EF 1和EF2以及堆栈2中的EF2。重新抛出始终为true。
-
仅本地调用。这覆盖了堆栈2中的EF1。重新抛出始终为false。
抛出异常时要非常小心。错误的用法(例如,重新抛出异常后立即调用ExecuteFunction())可能会导致您的应用程序崩溃或以难以调试的方式发生故障。