SpencerB
SpencerB

Reputation: 95

How to catch and handle QtWebEnginePage content changing?

In Qt's (now deprecated) QWebPage class, there was a signal contentsChanged() that was called whenever the html content of the web page was changed by either the user editing the page or the page being programmatically changed. There doesn't seem to be an equivalent signal in the newer QtWebEnginePage classes. Given that the method of making the web page editable from the Qt application has changed from the QWebPage::setContentEditable method to the native html5 javascript contenteditable, this doesn't really surprise me.

My question: Is there a way to "listen" for the QtWebEnginePage content changing? Is this something I have to now somehow handle from javascript?

Note that I looked in the porting information Qt provides, but found nothing that struck me as related...

Upvotes: 3

Views: 1325

Answers (1)

Dmitry
Dmitry

Reputation: 3143

Unfortunately, QtWebEngine has much worse and poorer integration with the rest of Qt facilities than QtWebKit. A lot of things QtWebKit was able to do out of the box are only possible via JavaScript trickery with QtWebEngine. I was able to solve this particular problem in the project of my own but it was not very easy.

First, I had to set up the interaction between C++ code and JavaScript code via QWebChannel. It is too large topic to cover within this answer but on the bright side, the documentation on this is more or less good: see Qt WebChannel JavaScript API and its examples. In the end from C++ code you should be able to do things like

webChannel->registerObject(QStringLiteral("myObject"), myObject);

where webChannel is a pointer to QWebChannel and myObject is a pointer to QObject subclass which has public slots. This action would allow the public slots of myObject be called by JavaScript code thus implementing the connection between JavaScript and C++.

The JavaScript's "native" solution for tracking the changes of DOM is MutationObserver. It is a JavaScript object one instantiates in order to listen to the DOM changes. On each DOM mutation the observer calls the callback function supplied to it at construction. In order to notify the C++ code of the change in DOM the callback should call the slot of some object exposed to JavaScript. In C++ code inside this slot you can react on the content change as you please - if there are multiple parts of C++ code which might want to subscribe to contentsChanged signal, provide them with such a signal, emit it from the slot of myObject.

Here's a simple example of MutationObserver setup on the JavaScript side:

var observer = MutationObserver(function(mutations, observer) {
    myObject.onContentChanged();
});

observer.start = function() {
    this.observe(document, {
        subtree: true,
        attributes: true,
        childList: true,
        characterData: true,
        characterDataOldValue: true
    });
}

observer.stop = function() {
    this.disconnect();
}

While this approach is significantly more complicated to set up than with QtWebKit's contentsChanged signal, in the end of the day I found it to be more flexible because with MutationObserver you can see what exactly has changed within DOM and do some advanced processing:

  1. Ignore the changes you are not interested in.
  2. Remember the previous DOM state for further undo/redo implementation purposes.
  3. Pause listening to DOM changes for periods when DOM needs to be changed by your manually executed JavaScript code - sometimes it's convenient to not notify the C++ code of such changes since it's hard for C++ code to distinguish between changes caused by text editing and changes caused by some script execution.
  4. Log the exact occurred change - was some node added or removed or changed etc.

Upvotes: 3

Related Questions