CEF中JavaScript与C++交互

913 阅读8分钟



在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方法。这个方法代码如下:

?

123456789101112131415161718192021222324252627282930313233343536373839404142434445<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()这个方法,代码如下:

?

12345678910111213141516171819202122232425<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。像下面这样:

?

123456789101112<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:

?

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990<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:

?

123456789101112131415161718192021222324252627282930313233<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交互的关键。代码如下:

?

1234567891011121314151617181920212223242526272829303132333435363738394041424344 <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代码可能如下:

?

12<code>alert(app.GetId());``</code>

当浏览器执行上面的代码时,ClientV8Handler的Execute方法会被调用。

好啦,现在来看ClientV8Handler的实现(V8Handler.cpp):

?

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 <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:

?

12<form>``     <code>UserName: <input id= "userName" type= "text" />&nbsp;&nbsp;Password: <input id= "password" type= "text" />&nbsp;&nbsp;<input onclick= "Login()" type= "button" value= "Login" /> </code></form>

Call into native by js extensions:

``

通过下面的命令可以测试:

?

12<code>cef_js_integration.exe --url=file: ///cef_js_integration.html``</code>