在Qml WebEngineView中创建Dom更改的侦听器(Mutation Observer)

时间:2019-07-09 10:43:06

标签: qt qml qtwebengine

我有一个包含以下内容的html页面:

<div id="ajaxloader" style="display: none;">
        <div>
          hello world
        </div>
</div>

我可以使用document.getElementById('ajaxloader').style.display读取其显示值
每当显示更改为blocknone时,我都希望得到通知。
目前,我正在使用计时器使用哑巴解决方案:

Timer{
        running : true
        repeat : true
        interval: 500
        onTriggered: {
            var js = "document.getElementById('ajaxloader').style.display"
            webView.runJavaScript(js,x=>{
                  if(x=="block"){
                       // we catch the change but its really bad solution                   
                  }
            })
        }
    }  

我正在寻找一种方法来捕获DOM中的此类更改,有一种名为Mutation Observer的东西,但是我不确定如何在QML的WebEngineView中实现它。
我需要的只是一种方法来捕获WebEngineView中发生的更改,或者是一个事件来捕获引擎中正在发生的CRUD,或者比这个计时器要好得多!
更新
例如,我们有一个前往google.com的网络引擎,加载完成后将搜索文本更改为“ hello world”,我们希望不使用计时器即可捕获该更改,在实际网站中,此更改实际上是通过CRUD函数(ajax请求)或其他方式:

WebChannel{
    id:_channel
}
WebEngineView{
    id:webEngine
    height: parent.height
    width: parent.width
    webChannel: _channel
    url : "www.google.com"
    onNewViewRequested: {
        request.openIn(webEngine)
    }
    objectName: "webView"
    profile.httpCacheType: WebEngineProfile.NoCache

    onLoadingChanged: {
        if(loadRequest.status == WebEngineView.LoadSucceededStatus){
            var js = "document.querySelector('input[role=combobox]').value = 'hello world'"
            webEngine.runJavaScript(js,y=>{})
        }
    }

}  

别忘了用C ++初始化引擎,如果没有以下命令,它将无法工作:QtWebEngine::initialize();和其他导入内容,此外,您需要将此内容添加到pro文件中 QT += webengine webengine-private webenginecore webenginecore-private
现在,如果我使用计时器方法将其放在一旁,它应该像这样:

Timer{
    running : true
    repeat : true
    interval : 500
    onTriggered:{
         var js = "document.querySelector('input[role=combobox]').value"
         webEngine.runJavaScript(js,y=>{console.log(y)});
         // now i have to check y to see if its equals to hello world or what ever which is really bad idea to use a timer here
    }
}  

例如,您可以像这样观察google输入的变化:

var targetNode = document.querySelector('input[role=combobox]')
targetNode.oninput = function(e){this.setAttribute('value',targetNode.value)}
var config = { attributes: true, childList: true, subtree: true };
var callback = function(mutationsList, observer) {
    for(var mutation of mutationsList) {
        if (mutation.type == 'childList') {
            console.log('A child node has been added or removed.');
        }
        else if (mutation.type == 'attributes') {
            console.log('The ' + mutation.attributeName + ' attribute was modified.');
        }
    }
};

// Create an observer instance linked to the callback function
var observer = new MutationObserver(callback);

// Start observing the target node for configured mutations
observer.observe(targetNode, config); 

2 个答案:

答案 0 :(得分:3)

该策略是在加载页面时加载qwebchannel.js,然后注入另一个脚本,该脚本使用Qt WebChannel与导出的对象建立连接

无需在C ++中创建QObject,可以使用QtObject。

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtWebEngine>

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);
    QtWebEngine::initialize();

    QString JsWebChannel;
    QFile file(":///qtwebchannel/qwebchannel.js");
    if(file.open(QIODevice::ReadOnly))
        JsWebChannel = file.readAll();

    QQmlApplicationEngine engine;
    engine.rootContext()->setContextProperty("JsWebChannel", JsWebChannel);
    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}

main.qml

import QtQuick 2.12
import QtQuick.Window 2.12
import QtWebChannel 1.13
import QtWebEngine 1.1

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    QtObject{
        id: listener
        WebChannel.id: "listener"
        property string text: ""
        onTextChanged: console.log(text) 

        property string script: "
            var listener;
            new QWebChannel(qt.webChannelTransport, function (channel) {
                listener = channel.objects.listener;

                var targetNode = document.querySelector('input[role=combobox]')
                targetNode.oninput = function(e){this.setAttribute('value',targetNode.value)}
                var config = { attributes: true, childList: true, subtree: true };
                var callback = function(mutationsList, observer) {
                    for(var mutation of mutationsList) {
                        if (mutation.type == 'childList') {
                            console.log('A child node has been added or removed.');
                        }
                        else if (mutation.type == 'attributes') {
                            console.log('The ' + mutation.attributeName + ' attribute was modified.');
                            listener.text = targetNode.value; // update qproperty
                        }
                    }
                };

                // Create an observer instance linked to the callback function
                var observer = new MutationObserver(callback);

                // Start observing the target node for configured mutations
                observer.observe(targetNode, config); 
            });
        "
    }
    WebChannel{
        id: channel
        registeredObjects: [listener]
        function inject(){
            webEngine.runJavaScript(JsWebChannel);
            webEngine.runJavaScript(listener.script);
        }
    }

    WebEngineView {
        id:webEngine
        url : "https://www.google.com/"
        profile.httpCacheType: WebEngineProfile.NoCache
        webChannel: channel
        anchors.fill: parent
        onLoadingChanged: {
            if(loadRequest.status == WebEngineView.LoadSucceededStatus){
                console.log("Page has successfully loaded")
                channel.inject()
            }
        }
    }
}

答案 1 :(得分:2)

如果您希望javascript调用C ++函数,则需要使用Qt WebChannel模块。

它将允许您将任何QObject派生对象公开给javascript,从而允许您从javascript调用公开对象的任何slot函数。

在这种情况下,您可以在JavaScript中使用MutationObserver并将C ++函数用作回调。

这是一个基本示例:

// C++
class MyObject: public QObject {
    Q_OBJECT
public slots:
    void mySlot();
}

int main() {
   ...

   // Create the channel and expose the object.
   // This part can also be done in QML
   QWebChannel channel;

   MyObject obj;
   channel.registerObject(QStringLiteral("myobject"), &obj);
   ...
}

// js
myobject = channel.objects.myobject;
observer = new MutationObserver(() => { myobject.mySlot(); }); // Will call MyObject::mySlot() in C++
observer.observe(...);

如果需要更多详细信息,可以查看以下示例:https://doc.qt.io/qt-5/qtwebchannel-standalone-example.html

Qt文档中还有其他示例,但请记住,js部分Qt WebChannel可在QWebEngine或任何Web浏览器中使用。因此,您会发现一些示例可能与您需要做的事情有所不同。

此外,您不能在跨C ++ / js边界的函数调用中传递任何参数类型