C++和JS交互V8原理:​​https://github.com/fanfeilong/cefutil/blob/master/doc/content_register_v8_extension.md​

CEF一个页面的框架如下图所示:

CEF:C++和JS交互_c++

CefBrowser:一个普通的浏览器页面(HTML)

CefFrame:每一个页面都由至少一个frame组成,最顶层的为mainframe

context:JS执行环境,每个frame都有自己独立的context,CEF中使用V8JavaScriptEngine解析和执行JS代码

CEF 中的JavaScript

  • CEF 利用 V8 JS 引擎来实现 JS。
  • 浏览器中的每一个 frame 都有自己的 JS 上下文,JS 只能在该上下文中执行。
  • JS 只能在渲染进程中的 TID_RENDERER 线程中执行。
  • 有关 JS 回调的接口都包含在 CefRenderProcessHandler 中,因此我们要实现这个接口来对 JS 进行扩展。这个接口一般由 CefApp 实现。

C++调用JS

事实上cef本身支持直接执行js(js代码或者js函数)的接口,原型如下:

///
// Execute a string of JavaScript code in this frame. The |script_url|
// parameter is the URL where the script in question can be found, if any.
// The renderer may request this URL to show the developer the source of the
// error. The |start_line| parameter is the base line number to use for error
// reporting.
///
/*--cef(optional_param=script_url)--*/
virtual void ExecuteJavaScript(const CefString& code,
const CefString& script_url,
int start_line) = 0;

///class CefV8Value,此时可以获取返回值
// Execute the function using the current V8 context. This method should only
// be called from within the scope of a CefV8Handler or CefV8Accessor
// callback, or in combination with calling Enter() and Exit() on a stored
// CefV8Context reference. |object| is the receiver ('this' object) of the
// function. If |object| is empty the current context's global object will be
// used. |arguments| is the list of arguments that will be passed to the
// function. Returns the function return value on success. Returns NULL if
// this method is called incorrectly or an exception is thrown.
///
/*--cef(optional_param=object)--*/
virtual CefRefPtr<CefV8Value> ExecuteFunction(
CefRefPtr<CefV8Value> object,
const CefV8ValueList& arguments) = 0;
1.ExecuteJavaScript 可以直接执行JS函数,但是没有办法获取返回值。
//@param js js代码或者函数,
//eg:js="add(1,2)"
void CBrowserHandler::ExeJs(const CefString &js)
{
...
browser->GetMainFrame()->ExecuteJavaScript(js, L"", 0);
...
}

#######################################C++调用JS测试,直接通过ExeJs("add(10,20)")调用JS函数####################
//JS:
function call_add(val1,val2)
{
alert(window.add(val1,val2));
}

//C++
...
if (m_browser_app)
m_browser_app->ExeJs(L"call_add(10,20)");
...

CEF:C++和JS交互_v8_02

2、ExecuteFunction需要配合V8Context一起执行。执行之前需要context->Enter(),完成之后context->Exit()

V8Context分为两种情况:

  1. 使用自己保存的V8Context (Render进程使用)
  2. 使用全局的V8Context (Render进程使用)
//获取局部保存的Context,并且执行Js函数
CefRefPtr<CefV8Context> context = m_map_call_back[iMsgID].first;
CefRefPtr<CefV8Value> call_back = m_map_call_back[iMsgID].second;
if (context && call_back)
{
context->Enter();
if (context->GetBrowser())
{
CefV8ValueList call_arguments;
call_arguments.push_back(CefV8Value::CreateString(args->GetString(2)));
if (call_back->IsFunction())
call_back->ExecuteFunction(NULL, call_arguments);
}
context->Exit();
}

//获取全局的JS函数并且执行GetV8Context只能在render进程中使用
CefRefPtr<CefV8Context> context = browser->GetMainFrame()->GetV8Context();
context->Enter();
CefRefPtr<CefV8Value> global = context->GetGlobal();
if (global)
{
//获取全局函数js_handler_call_back,并且执行js_handler_call_back
CefRefPtr<CefV8Value> call_back = global->GetValue("js_handler_call_back");
if (call_back && call_back->IsFunction())
{
CefV8ValueList call_arguments;
call_arguments.push_back(CefV8Value::CreateString(args->GetString(2)));
if (call_back->IsFunction())
call_back->ExecuteFunction(NULL, call_arguments);
}
}
context->Exit();

本次demo中,我们预定如果设置回调,则第一个参数必须是函数指针,否则我们将使用默认的全局回调函数js_handler_call_back回调数据。

function register(){
MyMath.register(call_back);
}

function MyTimer(){
MyMath.Timer(1);
}

<p onclick="register()">this is a register function</p>
<button onclick="MyTimer()">启动Timer(1)</button>

CEF:C++和JS交互_cef_03

JS调用C++(Render进程实现)

绑定值和函数到Window对象

在CefRenderProcessHandler::OnContextCreated() 中绑定一些值给 JS 的 window 对象。

class CRenderApp :public CefApp, public CefRenderProcessHandler
void CRenderApp::OnContextCreated(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefV8Context> context)
{
CEF_REQUIRE_RENDERER_THREAD();

//添加全局成员window的成员
CefRefPtr<CefV8Value> object = context->GetGlobal();
CefRefPtr<CefV8Value> v8Value = CefV8Value::CreateString("c++ value");
object->SetValue("MyValue", v8Value, V8_PROPERTY_ATTRIBUTE_NONE);

//添加全局函数,add函数实现在CJSHandler里面
CefRefPtr<CefV8Handler> add_handler = new CJSHandler(this);
CefRefPtr<CefV8Value> add_func = CefV8Value::CreateFunction("add", add_handler);
object->SetValue("add", add_func, V8_PROPERTY_ATTRIBUTE_NONE);
//此时支持window.MyValue 属性,和window.add函数
}

bool CJSAdd::Execute(const CefString& name,CefRefPtr<CefV8Value> object,const CefV8ValueList& arguments,CefRefPtr<CefV8Value>& retval,CefString& exception)
{
//function add
if (name == "add")
{
int32 val2 = arguments[0]->GetIntValue();
int32 val1 = arguments[1]->GetIntValue();
int32 sum = val1 + val2;
retval = CefV8Value::CreateInt(sum);
return true;
}
return false;
}

#######################################JS测试,直接通过window.MyValue访问####################
<script>
function show(){
alert(window.MyValue);
}

function call_add(val1,val2){
alert(window.add(val1,val2));
}
</script>

CEF:C++和JS交互_cef_04


CEF:C++和JS交互_javascript_05

扩展JS

在 CefRenderProcessHandler::OnWebKitInitialized() 中将 JS 脚本注入到 V8 中。
只能被Render 进程的调用

扩展值和函数

更详细的demo可以参考CefRegisterExtension 类的说明

void CRenderApp::OnWebKitInitialized()
{
CEF_REQUIRE_RENDERER_THREAD();

// Define the extension contents.
std::string extensionCode =
"var MyMath;"
"if (!MyMath)"//如果没有MyMath对象,则定义一个MyMath对象
" MyMath = {};"
"(function() {"
" MyMath.add = function(num1, num2) {"//定义一个MyMath的add函数,参数顺序为外部js调用顺序
" native function add();"//native 函数,不带参数
" return add(num1, num2);"//add在CefV8Handler具体实现,参数顺序为CefV8Handler 中的参数顺序
" };"
" MyMath.hellow = 'Hello JS!';"//定义一个MyMath的成员变量
"})();";

CefRefPtr<CefV8Handler> add_handler = new CJSAdd();
// Register the extension.
CefRegisterExtension("v8/test", extensionCode, add_handler);
}

###################JS测试,直接通过MyMath.add调用函数,MyMath.hellow获取成员变量####################
function CallMyMathAdd()
{
v1 = document.getElementById('num1').value;
v2 = document.getElementById('num2').value;
alert(MyMath.add(Number(v1),Number(v2)));
}

function ShowMyMathHellow()
{
alert(MyMath.hellow);
}

CEF:C++和JS交互_c++和js交互_06

CEF:C++和JS交互_javascript_07