CEF中JavaScript与C++交互
来源:互联网 发布:java调用scala程序 编辑:程序博客网 时间:2024/06/15 23:18
在CEF里,JS和Native(C/C++)代码可以很方便的交互,讲解得很清楚。我照着它实现了一个简单的交互示例。
在贴代码之前,先来看看Browser进程和Render进程是怎么回事儿,有什么不同。
Browser与Render进程
从cefsimple开始吧,cefsimple_win.cc中的wWinMain函数中调用了CefExecuteProcess()方法来检测是否要启动其它的子进程。此处的CefExecuteProcess是在libcef_dll_wrapper.cc中的,它内部又调用了cef_execute_process方法(libcef_dll.cc),cef_execute_process又调用了libcef/browser/context.cc文件内实现的CefExecuteProcess方法。这个方法代码如下:
<code>
int
CefExecuteProcess(
const
CefMainArgs& args,
CefRefPtr<cefapp> application,
void
* windows_sandbox_info) {
base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
#
if
defined(OS_WIN)
command_line.ParseFromString(::GetCommandLineW());
#
else
command_line.InitFromArgv(args.argc, args.argv);
#endif
// Wait for the debugger as early in process initialization as possible.
if
(command_line.HasSwitch(switches::kWaitForDebugger))
base::debug::WaitForDebugger(
60
,
true
);
// If no process type is specified then it represents the browser process and
// we do nothing.
std::string process_type =
command_line.GetSwitchValueASCII(switches::kProcessType);
if
(process_type.empty())
return
-
1
;
CefMainDelegate main_delegate(application);
// Execute the secondary process.
#
if
defined(OS_WIN)
sandbox::SandboxInterfaceInfo sandbox_info = {
0
};
if
(windows_sandbox_info == NULL) {
content::InitializeSandboxInfo(&sandbox_info);
windows_sandbox_info = &sandbox_info;
}
content::ContentMainParams params(&main_delegate);
params.instance = args.instance;
params.sandbox_info =
static_cast<sandbox::sandboxinterfaceinfo*>(windows_sandbox_info);
return
content::ContentMain(params);
#
else
content::ContentMainParams params(&main_delegate);
params.argc = args.argc;
params.argv = const_cast<
const
>(args.argv);
return
content::ContentMain(params);
#endif
</
const
></sandbox::sandboxinterfaceinfo*></cefapp></code>
它分析了命令行参数,提取”type”参数,如果为空,说明是Browser进程,返回-1,这样一路回溯到wWinMain方法里,然后开始创建Browser进程相关的内容。
如果”type”参数不为空,做一些判断,最后调用了content::ContentMain方法,直到这个方法结束,子进程随之结束。
content::ContentMain方法再追溯下去,就到了chromium的代码里了,在chromium/src/content/app/content_main.cc文件中。具体我们不分析了,感兴趣的可以去看看。
分析了CefExecuteProcess方法我们知道,Browser进程在cefsimple_win.cc内调用了CefExecuteProcess之后做了进一步的配置,这个是在simple_app.cc内完成的,具体就是SimpleApp::OnContextInitialized()这个方法,代码如下:
<code>
void
SimpleApp::OnContextInitialized() {
CEF_REQUIRE_UI_THREAD();
CefWindowInfo window_info;
window_info.SetAsPopup(NULL,
"cefsimple"
);
// SimpleHandler implements browser-level callbacks.
CefRefPtr<simplehandler> handler(
new
SimpleHandler());
// Specify CEF browser settings here.
CefBrowserSettings browser_settings;
std::string url;
CefRefPtr<cefcommandline> command_line =
CefCommandLine::GetGlobalCommandLine();
url = command_line->GetSwitchValue(
"url"
);
if
(url.empty())
url =
"http://www.google.com"
;
CefBrowserHost::CreateBrowser(window_info, handler.get(), url,
browser_settings, NULL);
}
</cefcommandline></simplehandler></code>
可以看到,这里创建SimpleHandler,并传递给CefBrowserHost::CreateBrowser去使用。
现在我们清楚了,Browser进程,需要CefApp(SimpleApp实现了这个接口)和CefClient(SimpleHandler实现了这个接口)。而Renderer进程只要CefApp。
另外,CEF还定义了CefBrowserProcessHandler和CefRenderProcessHandler两个接口,分别来处理Browser进程和Render进程的个性化的通知。因此,Browser进程的App一般还需要实现CefBrowserProcessHandler接口,Renderer进程的App一般还需要实现CefRenderProcessHandler接口。这里https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage有详细说明。
像cefsimple这个示例中的SimpeApp,没有实现CefRenderProcessHandler接口,没有针对Renderer进程做特别处理,所以当它作为Render进程时,会缺失一部分功能。比如JS与Native代码交互,这正是我们想要的。
如果要实现JS与Native代码交互,最好分开实现Browser进程需要的CefApp和Render进程需要的CefApp。像下面这样:
<code>
class
ClientAppRenderer :
public
CefApp,
public
CefRenderProcessHandler
{
...
}
class
ClientAppBrowser :
public
CefApp,
public
CefBrowserProcessHandler
{
...
}
</code>
当我们实现了CefRenderProcessHandler接口,就可以在其OnContextCreated()方法中获取到CefFrame对应的window对象,在它上面绑定一些JS函数或对象,然后JS代码里就可以通过window对象访问,如果是函数,就会调用到我们实现的CefV8Handler接口的Execute方法。
另外一种实现JS与Native交互的方式,是在实现CefRenderProcessHandler的OnWebKitInitialized()方法时导出JS扩展,具体参考https://bitbucket.org/chromiumembedded/cef/wiki/JavaScriptIntegration.md,有详细说明。
cef_js_integration项目
cef_js_integration是非常简单的示例,用来演示JS与Native的交互。它在一个项目内实现了ClientAppBrowser、ClientAppRenderer、ClientAppOther三种CefApp,分别对应Browser、Render及其它类别的三种进程。
JS和Native代码的交互发生在Render进程,App需要继承CefRenderProcessHandler来整合JS相关功能。因此在应用在启动时做了进程类型判断,根据不同的进程类型创建不同的CefApp。
这个示例演示了三种JS交互方式(参见https://bitbucket.org/chromiumembedded/cef/wiki/JavaScriptIntegration.md):
- 在native代码中通过CefFrame::ExecuteJavaScript()来执行JavaScript代码
- 将函数或对象绑定到CefFrame对应的window对象上,JS代码通过window对象访问native代码导出的函数或对象
- 使用CefRegisterExtension()注册JS扩展,JS直接访问注册到JS Context中的对象
这个项目参考了cefsimple、cefclient,还有https://github.com/acristoffers/CEF3SimpleSample,最终它比cefsimple复杂一点,比cefclient简单很多。
好啦,背景差不多,上源码。
cef_js_integration.cpp:
<code>#include <windows.h>
#include <tchar.h>
#include
"cef_js_integration.h"
#include <string>
#include
#include
"include/cef_app.h"
#include
"include/cef_browser.h"
#include
"ClientAppBrowser.h"
#include
"ClientAppRenderer.h"
#include
"ClientAppOther.h"
#include
"include/cef_command_line.h"
#include
"include/cef_sandbox_win.h"
//#define CEF_USE_SANDBOX 1
#
if
defined(CEF_USE_SANDBOX)
#pragma comment(lib,
"cef_sandbox.lib"
)
#endif
int
APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPTSTR lpCmdLine,
_In_
int
nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// Enable High-DPI support on Windows 7 or newer.
CefEnableHighDPISupport();
CefMainArgs main_args(hInstance);
void
* sandbox_info = NULL;
#
if
defined(CEF_USE_SANDBOX)
CefScopedSandboxInfo scoped_sandbox;
sandbox_info = scoped_sandbox.sandbox_info();
#endif
// Parse command-line arguments.
CefRefPtr<cefcommandline> command_line = CefCommandLine::CreateCommandLine();
command_line->InitFromString(::GetCommandLineW());
// Create a ClientApp of the correct type.
CefRefPtr<cefapp> app;
// The command-line flag won't be specified for the browser process.
if
(!command_line->HasSwitch(
"type"
))
{
app =
new
ClientAppBrowser();
}
else
{
const
std::string& processType = command_line->GetSwitchValue(
"type"
);
if
(processType ==
"renderer"
)
{
app =
new
ClientAppRenderer();
}
else
{
app =
new
ClientAppOther();
}
}
// Execute the secondary process, if any.
int
exit_code = CefExecuteProcess(main_args, app, sandbox_info);
if
(exit_code >=
0
)
return
exit_code;
// Specify CEF global settings here.
CefSettings settings;
#
if
!defined(CEF_USE_SANDBOX)
settings.no_sandbox =
true
;
#endif
// Initialize CEF.
CefInitialize(main_args, settings, app.get(), sandbox_info);
// Run the CEF message loop. This will block until CefQuitMessageLoop() is
// called.
CefRunMessageLoop();
// Shut down CEF.
CefShutdown();
return
0
;
}
</cefapp></cefcommandline></algorithm></string></tchar.h></windows.h></code>
可以看到,_tWinMain方法中解析了命令行参数,根据进程类型创建了不同的CefApp。这是它与cefsimple的区别。
ClientAppBrowser类与cefsimple示例中的SimpleApp基本一致,略过。
ClientAppRender类在ClientAppRender.h和ClientAppRender.cpp中实现。先是ClientAppRender.h:
<code>#ifndef CEF3_CLIENT_APP_RENDERER_H
#define CEF3_CLIENT_APP_RENDERER_H
#include
"include/cef_app.h"
#include
"include/cef_client.h"
#include
"V8handler.h"
class
ClientAppRenderer :
public
CefApp,
public
CefRenderProcessHandler
{
public
:
ClientAppRenderer();
CefRefPtr<cefrenderprocesshandler> GetRenderProcessHandler() OVERRIDE
{
return
this
;
}
void
OnContextCreated(
CefRefPtr<cefbrowser> browser,
CefRefPtr<cefframe> frame,
CefRefPtr<cefv8context> context);
void
OnWebKitInitialized() OVERRIDE;
private
:
CefRefPtr<clientv8handler> m_v8Handler;
IMPLEMENT_REFCOUNTING(ClientAppRenderer);
};
#endif
</clientv8handler></cefv8context></cefframe></cefbrowser></cefrenderprocesshandler></code>
ClientAppRender聚合了ClientV8Handler类的实例,回头再说。先来看ClientAppRender的OnContextCreated和OnWebKitInitialized,它们是实现JS与Native交互的关键。代码如下:
<code>#include
"ClientAppRenderer.h"
#include
"V8handler.h"
#include <windows.h>
#include <tchar.h>
ClientAppRenderer::ClientAppRenderer()
: m_v8Handler(
new
ClientV8Handler)
{
}
void
ClientAppRenderer::OnContextCreated(CefRefPtr<cefbrowser> browser,
CefRefPtr<cefframe> frame,
CefRefPtr<cefv8context> context)
{
OutputDebugString(_T(
"ClientAppRenderer::OnContextCreated, create window binding\r\n"
));
// Retrieve the context's window object.
CefRefPtr<cefv8value> object = context->GetGlobal();
// Create the "NativeLogin" function.
CefRefPtr<cefv8value> func = CefV8Value::CreateFunction(
"NativeLogin"
, m_v8Handler);
// Add the "NativeLogin" function to the "window" object.
object->SetValue(
"NativeLogin"
, func, V8_PROPERTY_ATTRIBUTE_NONE);
}
void
ClientAppRenderer::OnWebKitInitialized()
{
OutputDebugString(_T(
"ClientAppRenderer::OnWebKitInitialized, create js extensions\r\n"
));
std::string app_code =
"var app;"
"if (!app)"
" app = {};"
"(function() {"
" app.GetId = function() {"
" native function GetId();"
" return GetId();"
" };"
"})();"
;
CefRegisterExtension(
"v8/app"
, app_code, m_v8Handler);
}
</cefv8value></cefv8value></cefv8context></cefframe></cefbrowser></tchar.h></windows.h></code>
OnContextCreated给window对象绑定了一个NativeLogin函数,这个函数将由ClientV8Handler类来处理,当HTML中的JS代码调用window.NativeLogin时,ClientV8Handler的Execute方法会被调用。
OnWebKitInitialized注册了一个名为app的JS扩展,在这个扩展里为app定义了GetId方法,app.GetId内部调用了native版本的GetId()。HTML中的JS代码可能如下:
<code>alert(app.GetId());
</code>
当浏览器执行上面的代码时,ClientV8Handler的Execute方法会被调用。
好啦,现在来看ClientV8Handler的实现(V8Handler.cpp):
<code>#include
"V8handler.h"
#include <windows.h>
#include <tchar.h>
bool ClientV8Handler::Execute(
const
CefString& name,
CefRefPtr<cefv8value> object,
const
CefV8ValueList& arguments,
CefRefPtr<cefv8value>& retval,
CefString& exception)
{
if
(name ==
"NativeLogin"
)
{
if
(arguments.size() ==
2
)
{
CefString strUser = arguments.at(
0
)->GetStringValue();
CefString strPassword = arguments.at(
1
)->GetStringValue();
TCHAR szLog[
256
] = {
0
};
_stprintf_s(szLog,
256
, _T(
"user - %s, password - %s\r\n"
), strUser.c_str(), strPassword.c_str());
OutputDebugString(szLog);
//TODO: doSomething() in native way
retval = CefV8Value::CreateInt(
0
);
}
else
{
retval = CefV8Value::CreateInt(
2
);
}
return
true
;
}
else
if
(name ==
"GetId"
)
{
if
(arguments.size() ==
0
)
{
// execute javascript
// just for test
CefRefPtr<cefframe> frame = CefV8Context::GetCurrentContext()->GetBrowser()->GetMainFrame();
frame->ExecuteJavaScript(
"alert('Hello, I came from native world.')"
, frame->GetURL(),
0
);
// return to JS
retval = CefV8Value::CreateString(
"72395678"
);
return
true
;
}
}
// Function does not exist.
return
false
;
}
</cefframe></cefv8value></cefv8value></tchar.h></windows.h></code>
Execute在处理GetId方法时,还使用CefFrame::ExecuteJavaScript演示了如何在native代码中执行JS代码。
最后,来看一下html代码:
<script type="text/javascript"> function Login(){ window.NativeLogin(document.getElementById("userName").value, document.getElementById("password").value); } function GetId(){ alert("get id from native by extensions: " + app.GetId()); } </script>Call into native by Window bindings:
<form>
<code>UserName: <input id=
"userName"
type=
"text"
/> Password: <input id=
"password"
type=
"text"
/> <input onclick=
"Login()"
type=
"button"
value=
"Login"
/> </code></form>
Call into native by js extensions:
通过下面的命令可以测试:
<code>cef_js_integration.exe --url=file:
///cef_js_integration.html
</code>
- CEF中JavaScript与C++交互
- CEF中JavaScript与C++交互
- CEF中JavaScript与C++交互
- CEF方面的研究(五) CEF中C++与JS交互
- UIWebView中Objective-C与JavaScript交互
- WebView中JavaScript与Objective-C的交互
- iOS UIWebView中javascript与Objective-C交互、获取摄像头
- iOS UIWebView中javascript与Objective-C交互、获取摄像头
- iOS UIWebView中javascript与Objective-C交互、获取摄像头
- iOS UIWebView中javascript与Objective-C交互、获取摄像头
- iOS UIWebView中javascript与Objective-C交互
- iOS UIWebView中javascript与Objective-C交互、获取摄像头
- iOS UIWebView中javascript与Objective-C交互、获取摄像头
- iOS UIWebView中javascript与Objective-C交互、获取摄像头
- iOS UIWebView中javascript与Objective-C交互、获取摄像头
- Objective-c与javascript交互
- Objective-c与javascript交互
- objective-c与javascript交互
- Java集合类
- Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)
- error C4996: 'GetVersionExW': 被声明为已否决
- 原型模式
- mysql的优化
- CEF中JavaScript与C++交互
- 第4周项目3-单链表的应用(3)判断单链表是否递增
- Android——Activity生命周期与启动模式
- 使用Lambda表达式遍历集合:
- python函数的闭包问题(内部函数与外部函数详述)
- android的logcat的字符长度的限制,超过将直接截断
- 关于SurfaceView和事件的传递
- 自定义ViewGroup
- 实例构造器是不是静态方法?