1
votes

i have a html page containing this :

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

i can read its display value with document.getElementById('ajaxloader').style.display
i want to get notified whenever its display changed to block or none.
currently i am using a dumb solution using timer :

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

i am looking for a way to catch this kind of changes in DOM , there is something called Mutation Observer but i am not sure how to implement it into QML's WebEngineView.
all i need is a way to catch changes happend in the WebEngineView, or an event to catch CRUD's going on in the engine or anyway better than this timer!
UPDATE :
for example we have a webengine which goes to google.com and after load finished it changes search text to 'hello world' we want to catch that change without using timer , in real websites this changes actually happens with CRUD functions (ajax requests) or other ways:

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=>{})
        }
    }

}  

dont forget to initialize engine in c++ it wont work without this : QtWebEngine::initialize(); and other importing stuff plus you need to add this to pro file QT += webengine webengine-private webenginecore webenginecore-private
now if i use timer method which i want to put it aside it should be like this:

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
    }
}  

for example you can observe changes in input of google like this :

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
Your MutationObserver works only if the change is made by the user and not when the change is made programmatically, do you only want to detect the change when the user makes the change? - eyllanesc
@eyllanesc is there any way to include programmatically changes too? mutation or not any way will be ok - Mahdi Khalili

2 Answers

3
votes

The strategy is to load the qwebchannel.js when the page is loaded, and then inject another script that makes the connection with the exported object using Qt WebChannel

There is no need to create a QObject in C++, you can use a 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()
            }
        }
    }
}
2
votes

If you want javascript to call C++ functions, you need to use Qt WebChannel module.

It will let you expose any QObject derived object to javascript, allowing you to call any slot functions of the exposed object from javascript.

In you case, you can use in javascript a MutationObserver and use the C++ function as a callback.

Here is a basic example:

// 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(...);

If you want more details you can take a look at this example: https://doc.qt.io/qt-5/qtwebchannel-standalone-example.html

There are other examples in Qt documentation, but keep in mind that the js part Qt WebChannel works in QWebEngine or any web browser. So some of the examples you will find may differ from what you need to do.

Also you cannot pass any argument type in function calls across C++/js boundaries