Interaction between JavaScript and C ++ in CEF
In CEF, JS and Native (C/C ++) code can easily interact with each other and be clearly explained. I followed it to implement a simple interaction example.
Before you paste the code, let's take a look at the differences between the Browser process and the Render process.
Browser and Render processes
Start with cefsimple. The wWinMain function in cefsimple_win.cc calls the CefExecuteProcess () method to check whether other sub-processes are to be started. Here, CefExecuteProcess is in executor. It calls the cef_execute_process method (libcef_dll.cc) internally, and cef_execute_process calls the CefExecuteProcess method implemented in the libcef/browser/context. cc file. The code for this method is as follows:
int CefExecuteProcess(const CefMainArgs& args, CefRefPtr
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
(windows_sandbox_info); return content::ContentMain(params);#else content::ContentMainParams params(&main_delegate); params.argc = args.argc; params.argv = const_cast
(args.argv); return content::ContentMain(params);#endif
It analyzes the command line parameters and extracts the "type" parameter. If it is null, it indicates that it is a Browser process and-1 is returned. This way, It is traced back to the wWinMain method, then, create content related to the Browser process.
If the "type" parameter is not empty, make some judgments and call the content: ContentMain method until the method ends, and the child process ends.
The content: ContentMain method goes back to the chromium code, in the chromium/src/content/app/content_main.cc file. We will not analyze the details. If you are interested, you can check them out.
After analyzing the CefExecuteProcess method, we know that the Browser process has made further configuration after the CefExecuteProcess is called in cefsimple_win.cc. This is completed in simple_app.cc, specifically SimpleApp: OnContextInitialized () the code for this method is as follows:
void SimpleApp::OnContextInitialized() { CEF_REQUIRE_UI_THREAD(); CefWindowInfo window_info; window_info.SetAsPopup(NULL, "cefsimple"); // SimpleHandler implements browser-level callbacks. CefRefPtr
handler(new SimpleHandler()); // Specify CEF browser settings here. CefBrowserSettings browser_settings; std::string url; CefRefPtr
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);}
We can see that SimpleHandler is created here and passed to CefBrowserHost: CreateBrowser for use.
Now we know that the Browser process requires CefApp (SimpleApp implements this interface) and CefClient (SimpleHandler implements this interface ). The Renderer process only needs the CefApp.
In addition, CEF defines the CefBrowserProcessHandler and CefRenderProcessHandler interfaces to process personalized notifications of Browser and Render processes respectively. Therefore, the App of the Browser process generally needs to implement the CefBrowserProcessHandler interface, while the App of the Renderer process also needs to implement the CefRenderProcessHandler interface. Please refer to the following link for more information: https://bitbucket.org/chromiumembedded/cef/wiki/generalusage.
In the example of cefsimple, The SimpeApp does not implement the CefRenderProcessHandler interface and does not perform special processing on the Renderer process. Therefore, when it is used as a Render process, some functions are missing. For example, JS interacts with Native code, which is exactly what we want.
To implement interaction between JS and Native code, it is best to implement the CefApp required by the Browser process and the CefApp required by the Render process separately. As shown below:
class ClientAppRenderer : public CefApp, public CefRenderProcessHandler { ...}class ClientAppBrowser : public CefApp, public CefBrowserProcessHandler{ ...}
After implementing the CefRenderProcessHandler interface, we can get the window object corresponding to CefFrame in its OnContextCreated () method and bind some JS functions or objects to it, then the JS Code can be accessed through the window object. If it is a function, the Execute method of the implemented CefV8Handler interface will be called.
Another method for implementing interaction between JS and Native is to export JS extensions when implementing the OnWebKitInitialized () method of CefRenderProcessHandler. For details, refer to examples.
Cef_js_integration Project
Cef_js_integration is a simple example to demonstrate the interaction between JS and Native. In a project, it implements three cefapps: ClientAppBrowser, ClientAppRenderer, and ClientAppOther, which correspond to Browser, Render, and other processes.
Interaction between JS and Native code occurs in the Render process. The App must inherit the CefRenderProcessHandler to integrate JS-related functions. Therefore, when the application starts, it determines the process type and creates different cefapps based on different process types.
This example demonstrates three JS interaction methods (see https://bitbucket.org/chromiumembedded/cef/wiki/JavaScriptIntegration.md ):
-Use CefFrame: ExecuteJavaScript () in native code to execute JavaScript code
-Bind a function or object to the window object corresponding to CefFrame. JS Code accesses the function or object exported by native code through the window object.
-Use CefRegisterExtension () to register JS extensions. JS directly accesses the objects registered to JS Context.
For this project, refer to cefsimple, cefclient, and slave.
Well, the background is similar. Go to the source code.
Cef_js_integration.cpp:
#include
#include
#include "cef_js_integration.h"#include
#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")#endifint 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
command_line = CefCommandLine::CreateCommandLine(); command_line->InitFromString(::GetCommandLineW()); // Create a ClientApp of the correct type. CefRefPtr
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;}
As you can see, the _ tWinMain method parses the command line parameters and creates different cefapps based on the process type. This is the difference between it and cefsimple.
The ClientAppBrowser class is basically the same as SimpleApp in the cefsimple example, skipped.
The ClientAppRender class is implemented in ClientAppRender. h and ClientAppRender. cpp. ClientAppRender. h:
#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
GetRenderProcessHandler() OVERRIDE { return this; } void OnContextCreated( CefRefPtr
browser, CefRefPtr
frame, CefRefPtr
context); void OnWebKitInitialized() OVERRIDE;private: CefRefPtr
m_v8Handler; IMPLEMENT_REFCOUNTING(ClientAppRenderer);};#endif
ClientAppRender aggregates instances of the ClientV8Handler class. First, let's look at the OnContextCreated and OnWebKitInitialized of ClientAppRender. They are the key to implementing interaction between JS and Native. The Code is as follows:
#include "ClientAppRenderer.h"#include "V8handler.h"#include
#include
ClientAppRenderer::ClientAppRenderer() : m_v8Handler(new ClientV8Handler){}void ClientAppRenderer::OnContextCreated(CefRefPtr
browser, CefRefPtr
frame, CefRefPtr
context){ OutputDebugString(_T("ClientAppRenderer::OnContextCreated, create window binding\r\n")); // Retrieve the context's window object. CefRefPtr
object = context->GetGlobal(); // Create the "NativeLogin" function. CefRefPtr
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);}
OnContextCreated binds a NativeLogin function to the window object. This function will be handled by the ClientV8Handler class. When JS Code in HTML calls window. NativeLogin, The ClientV8Handler Execute method will be called.
OnWebKitInitialized registers a JS extension named app, which defines the GetId method for the app, and the app. GetId internally calls the native version of GetId (). The JS Code in HTML may be as follows:
alert(app.GetId());
When the Browser executes the above Code, the ClientV8Handler Execute method will be called.
Now let's take a look at the implementation of ClientV8Handler (V8Handler. cpp ):
#include "V8handler.h"#include
#include
bool ClientV8Handler::Execute(const CefString& name, CefRefPtr
object, const CefV8ValueList& arguments, CefRefPtr
& 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
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;}
When processing the GetId method, Execute also uses CefFrame: ExecuteJavaScript to demonstrate how to Execute JS Code in native code.
Finally, let's take a look at the html code:
<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:
Run the following command to test:
cef_js_integration.exe --url=file:///cef_js_integration.html
Well, the interaction between JS and Native is now available.