cef / JavaScript集成

介绍

Chromium和CEFV8 JavaScript引擎用于其内部JavaScript(JS)实现。浏览器中的每个框架都有其自己的JS上下文,该上下文为在该框架中执行的JS代码提供范围和安全性(有关更多信息,请参见“使用上下文”部分)。CEF公开了许多JS功能以集成到客户端应用程序中

使用CEF3 Blink(WebKit)和JS执行可在单独的渲染器进程中运行。渲染器进程中的主线程被标识为TID_RENDERER,并且所有V8执行都必须在此线程上进行。与JS执行相关的回调通过CefRenderProcessHandler接口公开。初始化新的渲染器进程时,可通过CefApp :: GetRenderProcessHandler()检索此接口。

在浏览器和渲染器进程之间通信的JS API应该使用异步回调进行设计。有关更多信息,请参见GeneralUsage Wiki页面的“异步JavaScript绑定”部分。

ExecuteJavaScript

从客户端应用程序执行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返回到客户端应用程序,请考虑使用“窗口绑定”或“扩展”。

窗口绑定

窗口绑定允许客户端应用程序将值附加到框架的window对象。窗口绑定是使用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>

每次重新加载框架时,窗口绑定都会重新加载,从而使客户端应用程序有必要在必要时更改绑定。例如,通过修改绑定到该框架的窗口对象的值,可以为不同的框架提供访问客户端应用程序中不同功能的权限。

扩展名

扩展类似于窗口绑定,只是扩展被加载到每一帧的上下文中,并且一旦加载就无法修改。加载扩展时,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>

 

基本JS类型

CEF支持创建基本的JS数据类型,包括未定义的,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();

JS数组

数组是使用CefV8Value :: CreateArray()静态方法创建的,该方法接受一个length参数。只能在上下文中创建和使用数组(有关更多信息,请参见“使用上下文”部分)。

// 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()变体,该变体将索引作为第一个参数。

JS对象

使用CefV8Value :: CreateObject()静态方法创建对象,该方法采用可选的CefV8Accessor参数。只能在上下文中创建和使用对象(有关更多信息,请参见“使用上下文”部分)。

CefRefPtr<CefV8Value> obj = CefV8Value::CreateObject(NULL);

使用SetValue()方法变量将值分配给对象,该方法变量将键字符串作为第一个参数。

obj->SetValue("myval", CefV8Value::CreateString("My String!"));

带访问器的对象

对象可以选择具有关联的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);

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

 函数和window对象绑定

函数可用于创建复杂的窗口绑定。

 

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>

功能和扩展

函数可用于创建复杂的扩展。请注意,使用扩展时需要使用“本机函数”前向声明。

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>

 使用上下文

浏览器窗口中的每个框架都有其自己的V8上下文。上下文定义了该框架中定义的所有变量,对象和函数的范围。如果当前代码位置在调用堆栈中具有较高的CefV8Handler,CefV8Accessor或OnContextCreated()/ OnContextReleased()回调,则V8将位于上下文内。

OnContextCreated()和OnContextReleased()方法定义与框架关联的V8上下文的完整寿命。使用这些方法时,请务必遵循以下规则:

  1. 在对该上下文的OnContextReleased()调用之后,请勿保留或使用V8上下文引用。

  2. 未指定所有V8对象的寿命(直到GC)。直接维护从V8对象到您自己的内部实现对象的引用时要小心。在许多情况下,最好使用一个代理对象,该应用程序将您的应用程序与V8上下文相关联,并且在为上下文调用OnContextReleased()时可以“断开连接”(允许释放内部实现对象)。

如果V8当前不在上下文中,或者如果您需要检索和存储对上下文的引用,则可以使用两个可用的CefV8Context静态方法之一。GetCurrentContext()返回当前正在执行JS的框架的上下文。GetEnteredContext()返回JS执行开始的框架的上下文。例如,如果frame1中的函数调用frame2中的函数,则当前上下文将为frame2,而输入的上下文将为frame1。

如果V8在上下文中,则只能创建,修改和执行数组,对象和函数,对于函数而言,只能执行。如果V8不在上下文中,则应用程序需要通过调用Enter()进入上下文,并通过调用Exit()退出上下文。仅应使用Enter()和Exit()方法:

  1. 在现有上下文之外创建V8对象,函数或数组时。例如,在创建JS对象以响应本机菜单回调时。

  2. 在当前上下文以外的上下文中创建V8对象,函数或数组时。例如,如果源自frame1的呼叫需要修改frame2的上下文。

执行功能

本机代码可以使用ExecuteFunction()和ExecuteFunctionWithContext()方法执行JS函数。仅当V8已在上下文中(如“使用上下文”一节中所述)时,才应使用ExecuteFunction()方法。ExecuteFunctionWithContext()方法允许应用程序指定要输入的执行上下文。

使用JS回调

在使用本机代码注册JS函数回调时,应用程序应在本机代码中存储对当前上下文和JS函数的引用。这可以如下实现。

1.在OnJSBinding()中创建一个“注册”函数。

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

2.在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;
}

3.通过JavaScript注册JS回调。

<script language="JavaScript">
function myFunc() {
  // do something in JS.
}
window.register(myFunc);
</script>

4.稍后执行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绑定”部分。

抛出异常

如果在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设置两个呼叫站点来实现:

  1. 仅从V8处理程序调用。这涵盖了堆栈1中的EF 1和EF2以及堆栈2中的EF2。重新抛出始终为true。

  2. 仅本地调用。这覆盖了堆栈2中的EF1。重新抛出始终为false。

抛出异常时要非常小心。错误的用法(例如,重新抛出异常后立即调用ExecuteFunction())可能会导致您的应用程序崩溃或以难以调试的方式发生故障。

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页