CEF JS与browser进程间的异步通信
2016-03-02 15:27
716 查看
基于CEF开发时经常需要在JS和C++代码间通信,我们在CEF中JavaScript与C++交互中讨论了常见的交互方式,不过都是在Renderer进程中,这次来看看如何在JS和Browser进程间通信,基本介绍可以看这里:
https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage#markdown-header-asynchronous-javascript-bindings。
具体做法,上面给出的链接里有大概说明,另外在cef_message_router.h中有详细说明,它分了9步,有需要的可以去查看。我试验了下可行。按照我的实验,从三个方面来说说:
browser进程
renderer进程
html中的js
foruok原创,转载请关注微信订阅号“程序视界”联系foruok。
使用CefMessageRouterConfig来定义可以在html中使用的方法名字。代码片段可能如下:
注意我在main函数之外定义了一个CefMessageRouterConfig的实例,然后在main函数中设置了query和cancel这两个函数。它们定义了可以在html的js代码里使用的方法,比如根据上面代码,你可以用window.cefQuery({…})。
2)创建CefMessageRouterBrowserSide的实例
browser进程这边需要一个CefMessageRouterBrowserSide的实例来处理renderer进程发过来的消息。建议把这个实例作为成员变量放在CefClient的派生类中,因为它CefMessageRouterBrowserSide定义了一些方法,需要在指定的地方调用。
声明和初始化的代码片段:
3)调用预定义方法
CefMessageRouterBrowserSide预定义了下列接口,需要在指定地方调用:
所以,我们的这个ClientHandler类,声明如下:
ClientHandler要实现CefClient的OnProcessMessageReceived、CefLifeSpanHandler的OnBeforeClose、CefRequestHandler的OnRenderProcessTerminated和OnBeforeBrowse方法,在这些方法里调用CefMessageRouterBrowserSide的同名方法即可。代码片段如下:
4) 在browser进程里实现处理JS查询的handler
CefMessageRouterBrowserSide定义了一个内部类Handler,继承它:
实现 OnQuery 和 OnQueryCanceled两个方法:
5)给CefMessageRouterBrowserSide的实例添加handler
我在ClientHandler类中定义了一个BrowserSideJSHandler的实例,类似下面:
然后在ClientHandler构造函数中加入了下列代码:
上面的代码把BrowserSideJSHandler加入到browser进程里的消息路由器中。
好了,到这里browser进程配置完成。接下来看renderer进程。
renderer进程的配置和browser必须一致!
因为我browser和renderer使用一个进程,所以前面的代码可以共用,不说了。
2)创建CefMessageRouterRendererSide的实例
renderer进程这边需要一个CefMessageRouterRendererSide的实例来处理browser进程发过来的消息和js传入的信息。建议把这个实例作为成员变量放在CefApp的派生类中。
我的定义类似下面:
创建类似下面:
3) 调用预定义方法
CefMessageRouterRendererSide定义了下面三个预定义方法:
这三个方法需要在CefRenderProcessHandler的派生类的同名方法里调用,我的代码片段:
好啦,renderer进程这边的事儿干完了。接下来就可以在html里写js代码调用cefQuery方法了。
关键的代码起始就一个函数:
cefQuery方法接受一个JS对象,如你所见,request、persistent可以对应到BrowserSideJSHandler::OnQuery的参数上,onSuccess和onFailure则在renderer进程里做了转换,到browser进程里对应到了CefMessageRouterBrowserSide::Callback的Success和Failure方法上。
我的实现,对HelloCefQuery调用success,对GiveMeMoney调用failure,其它的都没处理。
成功时的效果:
失败时的效果:
查询未被处理时的效果:
CefMessageRouterRendererSide在renderer进程内,在OnContextCreated中向JS导出了cefQuery和cefQueryCancel两个方法,具体见cef_message_router.cc
renderer和browser进程间使用SendProcessMessage通信,在OnProcessMessageReceived中处理消息
就这样吧。
其他参考文章详见我的专栏:【CEF与PPAPI开发】。
https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage#markdown-header-asynchronous-javascript-bindings。
具体做法,上面给出的链接里有大概说明,另外在cef_message_router.h中有详细说明,它分了9步,有需要的可以去查看。我试验了下可行。按照我的实验,从三个方面来说说:
browser进程
renderer进程
html中的js
foruok原创,转载请关注微信订阅号“程序视界”联系foruok。
browser进程
1)配置browser进程这一侧的Message Router使用CefMessageRouterConfig来定义可以在html中使用的方法名字。代码片段可能如下:
[code]... CefMessageRouterConfig g_messageRouterConfig; 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(); g_messageRouterConfig.js_query_function = "cefQuery"; g_messageRouterConfig.js_cancel_function = "cefQueryCancel"; CefMainArgs main_args(hInstance); ... }
注意我在main函数之外定义了一个CefMessageRouterConfig的实例,然后在main函数中设置了query和cancel这两个函数。它们定义了可以在html的js代码里使用的方法,比如根据上面代码,你可以用window.cefQuery({…})。
2)创建CefMessageRouterBrowserSide的实例
browser进程这边需要一个CefMessageRouterBrowserSide的实例来处理renderer进程发过来的消息。建议把这个实例作为成员变量放在CefClient的派生类中,因为它CefMessageRouterBrowserSide定义了一些方法,需要在指定的地方调用。
声明和初始化的代码片段:
[code]CefRefPtr<CefMessageRouterBrowserSide> m_browser_side_router; ... extern CefMessageRouterConfig g_messageRouterConfig; m_browser_side_router = CefMessageRouterBrowserSide::Create(g_messageRouterConfig); ...
3)调用预定义方法
CefMessageRouterBrowserSide预定义了下列接口,需要在指定地方调用:
[code]// call from CefClient::OnProcessMessageReceived bool OnProcessMessageReceived(...) // call from CefLifeSpanHandler::OnBeforeClose() void OnBeforeClose(CefRefPtr<CefBrowser> browser) // call from CefRequestHandler::OnRenderProcessTerminated() void OnRenderProcessTerminated(CefRefPtr<CefBrowser> browser) // call from CefRequestHandler::OnBeforeBrowse() void OnBeforeBrowse(...)
所以,我们的这个ClientHandler类,声明如下:
[code]class ClientHandler : public CefClient, public CefDisplayHandler, public CefLifeSpanHandler, public CefLoadHandler, public CefRequestHandler { ... }
ClientHandler要实现CefClient的OnProcessMessageReceived、CefLifeSpanHandler的OnBeforeClose、CefRequestHandler的OnRenderProcessTerminated和OnBeforeBrowse方法,在这些方法里调用CefMessageRouterBrowserSide的同名方法即可。代码片段如下:
[code]void ClientHandler::OnBeforeClose(CefRefPtr<CefBrowser> browser) { CEF_REQUIRE_UI_THREAD(); m_browser_side_router->OnBeforeClose(browser); ... } bool ClientHandler::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser, CefProcessId source_process, CefRefPtr<CefProcessMessage> message) { if (m_browser_side_router->OnProcessMessageReceived(browser, source_process, message)) return true; return false; } bool ClientHandler::OnBeforeBrowse(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefRequest> request, bool is_redirect) { m_browser_side_router->OnBeforeBrowse(browser, frame); return false; } void ClientHandler::OnRenderProcessTerminated(CefRefPtr<CefBrowser> browser, TerminationStatus status) { m_browser_side_router->OnRenderProcessTerminated(browser); }
4) 在browser进程里实现处理JS查询的handler
CefMessageRouterBrowserSide定义了一个内部类Handler,继承它:
[code]class BrowserSideJSHandler : public CefMessageRouterBrowserSide::Handler { public: BrowserSideJSHandler(); virtual bool OnQuery(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, int64 query_id, const CefString& request, bool persistent, CefRefPtr<Callback> callback); virtual void OnQueryCanceled(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, int64 query_id); };
实现 OnQuery 和 OnQueryCanceled两个方法:
[code]bool BrowserSideJSHandler::OnQuery(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, int64 query_id, const CefString& request, bool persistent, CefRefPtr<Callback> callback) { if (request == "HelloCefQuery") { callback->Success("HelloCefQuery Ok"); return true; } else if (request == "GiveMeMoney") { callback->Failure(404, "There are none thus query!"); return true; } return false; // give other handler chance } void BrowserSideJSHandler::OnQueryCanceled(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, int64 query_id) { //cancel our async query task... }
5)给CefMessageRouterBrowserSide的实例添加handler
我在ClientHandler类中定义了一个BrowserSideJSHandler的实例,类似下面:
[code]class ClientHandler : public CefClient, public CefDisplayHandler, public CefLifeSpanHandler, public CefLoadHandler, public CefRequestHandler { ... CefRefPtr<CefMessageRouterBrowserSide> m_browser_side_router; BrowserSideJSHandler m_jsHandler; ... }
然后在ClientHandler构造函数中加入了下列代码:
[code]m_browser_side_router->AddHandler(&m_jsHandler, true);
上面的代码把BrowserSideJSHandler加入到browser进程里的消息路由器中。
好了,到这里browser进程配置完成。接下来看renderer进程。
renderer进程
1)配置renderer进程这一侧的Message Routerrenderer进程的配置和browser必须一致!
因为我browser和renderer使用一个进程,所以前面的代码可以共用,不说了。
2)创建CefMessageRouterRendererSide的实例
renderer进程这边需要一个CefMessageRouterRendererSide的实例来处理browser进程发过来的消息和js传入的信息。建议把这个实例作为成员变量放在CefApp的派生类中。
我的定义类似下面:
[code]class ClientAppRenderer : public CefApp, public CefRenderProcessHandler { ... CefRefPtr<CefMessageRouterRendererSide> m_renderer_side_router; ... };
创建类似下面:
[code]m_renderer_side_router = CefMessageRouterRendererSide::Create(g_messageRouterConfig);
3) 调用预定义方法
CefMessageRouterRendererSide定义了下面三个预定义方法:
[code]void OnContextCreated(...) void OnContextReleased(...) bool OnProcessMessageReceived(...)
这三个方法需要在CefRenderProcessHandler的派生类的同名方法里调用,我的代码片段:
[code]void ClientAppRenderer::OnContextCreated(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context) { m_renderer_side_router->OnContextCreated(browser, frame, context); } void ClientAppRenderer::OnContextReleased(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context) { m_renderer_side_router->OnContextReleased(browser, frame, context); } bool ClientAppRenderer::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser, CefProcessId source_process, CefRefPtr<CefProcessMessage> message) { if (m_renderer_side_router->OnProcessMessageReceived(browser, source_process, message)) return true; return false; }
好啦,renderer进程这边的事儿干完了。接下来就可以在html里写js代码调用cefQuery方法了。
html中的js
html这一侧很简单,我的测试代码如下:[code]<!DOCTYPE html> <html> <!-- Copyright (c) 2016 foruok@微信订阅号“程序视界”(programmer_sight). All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> <head> <script type="text/javascript"> function doQuery() { window.cefQuery({ request: document.getElementById("query").value, persistent: false, onSuccess: function(response) { alert(response); }, onFailure: function(code, msg) { alert(code + " - " + msg); } } ); } </script> <meta charset="UTF-8"> <title>Test Cef Query</title> </head> <body> <form> Query String: <input type="text" id="query" value="HelloCefQuery"/> <input type="button" value="SendQuery" onclick="doQuery()"/> </form> </body> </html>
关键的代码起始就一个函数:
[code]function doQuery() { window.cefQuery({ request: document.getElementById("query").value, persistent: false, onSuccess: function(response) { alert(response); }, onFailure: function(code, msg) { alert(code + " - " + msg); } } ); }
cefQuery方法接受一个JS对象,如你所见,request、persistent可以对应到BrowserSideJSHandler::OnQuery的参数上,onSuccess和onFailure则在renderer进程里做了转换,到browser进程里对应到了CefMessageRouterBrowserSide::Callback的Success和Failure方法上。
我的实现,对HelloCefQuery调用success,对GiveMeMoney调用failure,其它的都没处理。
成功时的效果:
失败时的效果:
查询未被处理时的效果:
Cef Generic Message Router的实现
Generic Message Router内部实现有两个关键点:CefMessageRouterRendererSide在renderer进程内,在OnContextCreated中向JS导出了cefQuery和cefQueryCancel两个方法,具体见cef_message_router.cc
renderer和browser进程间使用SendProcessMessage通信,在OnProcessMessageReceived中处理消息
就这样吧。
其他参考文章详见我的专栏:【CEF与PPAPI开发】。
相关文章推荐
- 常用JS脚本
- HTML5的兼容问题以及调用js文件的方法
- Extjs下拉列表树形图
- java和JavaScript 正则区别
- 编写高质量JavaScript代码的基本要点
- JS基础(一)
- JavaScript 的原型对象 Prototype
- JS获取当前脚本文件的绝对路径
- JS核心知识点:DOM\BOM\EVENT
- 10个应该避免的ExtJS开发实践
- JS"类"中方法的互相调用
- JS判断字符串长度的5个方法
- WebSocket的JavaScript例子
- javascript 高级程序设计 二
- Json相关
- 浅谈JS原型对象和原型链
- js中实现输入框输入字数提示
- jsp常用功能
- 禁用和启用链接(a元素|LinkButton)的js方法
- js实现表格字段本地排序