首页 > 解决方案 > 为 Qml WebEngineView 中的 Dom 更改创建侦听器(Mutation Observer)

问题描述

我有一个包含这个的 html 页面:

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

我可以读取它的显示值,document.getElementById('ajaxloader').style.display
我想在它的显示变为block或时得到通知none
目前我正在使用一个使用计时器的愚蠢解决方案:

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); 

标签: qtqmlqtwebengine

解决方案


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

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

主文件

#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()
            }
        }
    }
}

推荐阅读