CEF浏览器客户端实现与JS交互的功能
背景
之前的文章已经基本实现了浏览器的常用功能,如网页的加载、跳转、前进、后退、刷新、控制台窗口、设置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的全部交互功能。
- 点赞
- 收藏
- 关注作者
评论(0)