背景
之前的文章已经基本实现了浏览器的常用功能,如网页的加载、跳转、前进、后退、刷新、控制台窗口、设置 cookie 等。
除了这些基本功能,我们自定义浏览器客户端时,有时是需要与自己的网页进行交互的,比如我们的客户端要调用网页的某个方法、或者网页需要调用客户端程序的某个方法。这时我们就需要实现浏览器客户端与 js 的交互功能。
这里依然是在 CefSimple 示例的基础上进行的拓展,可以结合之前的文章一起看。
具体实现
cef 调用 js 方法
首先,获取浏览器窗口类。 参见:https://juejin.cn/post/7120536782281637918
在 simple_handler.h 文件中有一个浏览器窗口列表变量,里面存储了所有创建了的 CefBrowser,所以我们想要获取浏览器窗口类,就需要在 simple_handler.h 文件中添加一个 getBrowser()函数,返回一个 CefBrowser。
//simple_handler.h
CefRefPtr<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_OBJECT
public:
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 的全部交互功能。
评论