CEF浏览器客户端实现与JS交互的功能

举报
黑兔子 发表于 2022/09/15 13:26:03 2022/09/15
【摘要】 我们自定义浏览器客户端时,有时是需要与自己的网页进行交互的,比如我们的客户端要调用网页的某个方法、或者网页需要调用客户端程序的某个方法。这时我们就需要实现浏览器客户端与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的全部交互功能。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。