背景
之前的文章已经基本实现了浏览器的常用功能,如网页的加载、跳转、前进、后退、刷新、控制台窗口、设置 cookie 等。
除了这些基本功能,我们自定义浏览器客户端时,有时是需要与自己的网页进行交互的,比如我们的客户端要调用网页的某个方法、或者网页需要调用客户端程序的某个方法。这时我们就需要实现浏览器客户端与 js 的交互功能。
这里依然是在 CefSimple 示例的基础上进行的拓展,可以结合之前的文章一起看。
具体实现
cef 调用 js 方法
首先,获取浏览器窗口类。 参见:https://juejin.cn/post/7120536782281637918
在 simple_handler.h 文件中有一个浏览器窗口列表变量,里面存储了所有创建了的 CefBrowser,所以我们想要获取浏览器窗口类,就需要在 simple_handler.h 文件中添加一个 getBrowser()函数,返回一个 CefBrowser。
//simple_handler.hCefRefPtr<CefBrowser> getBrowser();
复制代码
//simple_handler.cpp//如果集合不为空,获取集合中的第一个CefBrowser元素。CefRefPtr<CefBrowser> SimpleHandler::getBrowser(){ if(!browser_list_.empty()) { return browser_list_.front(); } return NULL;}
复制代码
然后,在自定义 Widget 中进行调用。
CEF 有专门的调用 js 方法的函数:ExecuteJavaScript,它是一个属于 CefFrame 类的方法。所以我们想要调用 js 的方法,只需要获取到页面的 frame,然后调用 ExecuteJavaScript 就可以了。
参数一是要执行的 js 语句;参数二是可以找到问题脚本(如果有的话)的 URL。渲染器可能会请求这个 URL 来向开发人员显示错误的来源;第三个参数是用于错误报告的基线编号。
void CefBrowserWidget::ExecuteJS(QString js){ if(m_cefHandler->getBrowser()) { CefRefPtr<CefFrame> frame = m_cefHandler->getBrowser()->GetMainFrame(); if (frame) { frame->ExecuteJavaScript(js.toStdWString(), frame->GetURL(), 0); } }}
复制代码
js 调用 cef 方法
js 调用 cef 有两种方法,一种是窗口绑定,一种是 js 扩展。
窗口绑定是在 CefRenderProcessHandler::OnContextCreated 创建 V8 对象,将 V8 对象注册到 context 中;
js 拓展是在 CefRenderProcessHandler::OnWebKitInitialized 中注册新的 V8 扩展关联指定的 js。
我们这里用的是第一种方法。
- 首先,修改 SimpleApp.h,让 SimpleApp 继承 CefRenderProcessHandler,并实现 OnContextCreated 方法。
class SimpleApp : public QObject, public CefApp, public CefBrowserProcessHandler, public CefRenderProcessHandler{ ... virtual CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler() OVERRIDE { return this;} // CefRenderProcessHandler methods: virtual void OnContextCreated(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context) OVERRIDE; ...};
复制代码
然后,实现 OnContextCreated 方法。
void SimpleApp::OnContextCreated(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context){ // Retrieve the context's window object. CefRefPtr<CefV8Value> object = context->GetGlobal(); // Create an instance of my CefV8Handler object. //ClientV8Handler就是我们要创建的V8对象,这个类是我们自定义的一个类,见下一步。 ClientV8Handler* handler = new ClientV8Handler(); // Create the "myfunc" function.这里的recvRenderMsg就是js调的方法名。 CefRefPtr<CefV8Value> func = CefV8Value::CreateFunction("recvRenderMsg", handler); // Add the "myfunc" function to the "window" object. object->SetValue("recvRenderMsg", func, V8_PROPERTY_ATTRIBUTE_NONE);}
复制代码
定义 ClientV8Handler 类,实现接收调用。
#include <QObject>#include "include/cef_v8.h"
class ClientV8Handler : public QObject, public CefV8Handler{ Q_OBJECTpublic: ClientV8Handler(); virtual bool Execute(const CefString& name, CefRefPtr<CefV8Value> object, const CefV8ValueList& arguments, CefRefPtr<CefV8Value>& retval, CefString& exception) OVERRIDE;private: // Provide the reference counting implementation for this class. IMPLEMENT_REFCOUNTING(ClientV8Handler);};
复制代码
这里最重要的就是 Execute 方法的实现。
bool ClientV8Handler::Execute(const CefString &name, CefRefPtr<CefV8Value> object, const CefV8ValueList &arguments, CefRefPtr<CefV8Value> &retval, CefString &exception){ // 判断js调用的方法名称 if (name == "recvRenderMsg") { // 构造消息 CefRefPtr<CefProcessMessage> recvRenderMsg =CefProcessMessage::Create("recvRenderMsg"); // 该方法的参数,是以列表形式获取的 CefRefPtr<CefListValue> args = recvRenderMsg->GetArgumentList(); args->SetSize(2); QString funcName = QString::fromStdWString(arguments.at(0)->GetStringValue()); QString funcValue = QString::fromStdWString(arguments.at(1)->GetStringValue()); args->SetString(0, funcName.toStdWString()); args->SetString(1, funcValue.toStdWString()); // 发送消息 CefV8Context::GetCurrentContext()->GetBrowser()->GetFocusedFrame() ->SendProcessMessage(PID_BROWSER, recvRenderMsg); // 该方法的返回值 //retval = CefV8Value::CreateString("recvRenderMsg!"); return true; } // Function does not exist. return false;}
复制代码
这里需要注意的是 CefBrowserProcess 和 CefRenderProcess 是 cef 的两个进程,之前我们实现过得功能全部都在 CefBrowserProcess 进程中,而 js 调用 cef 方法的功能是在 CefRenderProcess 进程中。这是两个单独的进程,但是都是通过 SimpleApp 类继承并实现的。但是它们两个进程之间是互不相同的,需要通过发送消息的方式实现数据交换,即 SendProcessMessage。然后在 CefBrowserProcess 进程中进行消息的接收。
所以在程序运行时,你再 Execute 中执行 qDebug,是不会有输出显示在输出栏中的。想要通过发送信号的方式也是不行的。
接收消息。
在 simple_handler.h 文件中添加 OnProcessMessageReceived。
virtual bool OnProcessMessageReceived(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefProcessId source_process, CefRefPtr<CefProcessMessage> message) OVERRIDE;
复制代码
在 simple_handler.cpp 中进行实现。
bool ClientHandler::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefProcessId source_process, CefRefPtr<CefProcessMessage> message){ const std::string& messageName = message->GetName(); if (messageName == "recvRenderMsg") // 消息的名称 { // 获取消息的参数 CefRefPtr<CefListValue> args = message->GetArgumentList(); CefString funcName = args->GetString(0); CefString funcValue = args->GetString(1); // 这个时候可以发送消息给想要接收的地方了 emit sigMsg(QString::fromStdWString(funcName), QString::fromStdWString(funcValue)); return true; } return false;}
复制代码
这样就实现了 cef 与 js 的全部交互功能。
评论