How to use Qt WebEngine and QWebChannel?

22.3k Views Asked by At

I'm using the new WebEngine to play around and learn. I've been trying to find some similar methods found using Qt WebKit: addToJavaScriptWindowObject()

I found that using Qt WebEngine, I have to use the QWebChannel to register functions to the JavaScript window object. If this is correct, it takes me to the following question.

I've installed Qt 5.4.0 on my computer. I noticed that qwebchannel.js is not found in the SDK installed on my computer. I found it on the Git source.

If I have a Qt native desktop application with a QWebEnginePage and QWebEngineView, what do I need to be able to register functions on the JavaScript window object?

My desktop application navigates automatically to a http page that I have created. So I have access to the content connected to the QWebEngineView.

What are the steps to take so I can make this work?

4

There are 4 best solutions below

0
On

An alternative and much simpler way of communication with the page is to use runJavaScript function:

view->page()->runJavaScript("alert('Hello from C++');");

It has its limitations: the call must be initiated from the C++ side and you can get only synchronous response from JS. But there is an upside too: no modification of the underlying webpage is necessary.

Currently opened webpage can be accessed using QWebEngineView::page() function, as in the example above. During the navigation, the browser doesn't change the page until the next one is received from the network, so this function returns valid page object at any time. But your JS may still interrupt new page loading in a way that you'll appear in the document.readyState == 'loading' where the DOM tree is not yet constructed and some scripts on the page might not have been run yet. In this case you should wait for the DOMContentLoaded event.

0
On

Qt has documentation on this now:

Qt WebChannel Standalone Example

You have to add a QWebSocketServer to your cpp app that the QWebEngineView's HTML/Javascript will connect to using a WebSocket. Then use QWebChannel for two way communication.

0
On

I will summarize your questions as following:

  1. Do I need QWebChannel to register JavaScript functions in the WebEngine?
  2. Where can I find QWebChannel.js
  3. How to communicate JS to C++ and C++ to JS

I will be explaining the code step-by-step. In the following snippet, there are sections marked as DEFINITIONS, SETUP, and TEST. Following code will be inserted there.

First, let take a simple code to play with:

#include <QApplication>
#include <QDebug>
#include <QWebEngineView>
#include <QWebChannel>

// ... DEFINITIONS HERE

auto main( int argn, char* argv[] )-> int
{
    QApplication app(argn, argv);
    QWebEngineView browser;
    browser.resize(QSize(800,600));
    browser.show();
    browser.load(QUrl("http://www.wikipedia.org"));
    
    // .. SETUP HERE
    
    QObject::connect(&browser, &QWebEngineView::loadFinished, [&browser](bool ok)
    { 
        qDebug()<<"Load Finished " << ok;
        
        // TEST CODE HERE
    ));
    
    return app.exec();
}

Explanation: This code creates a Qt application, creates a QWebEngineView and set some minimal properties to make it visible. A page from 'Wikipedia' is loaded inside and a signal/slot event is connected to print some log when the page is finally loaded.

How to call JS functions from C++ ?

You can simply call JS using QWebEnginePage::runJavaScript as following. Add this code to the TEST CODE HERE.

QString code = QStringLiteral(
R"DELIM(

var links = document.getElementsByTagName('a');
for ( var i=0; i<links.length; ++i)
{
    links[i].style.backgroundColor = 'yellow';
};
)DELIM");
browser.page()->runJavaScript(code, 42);

Explanation: This code execute some JS into the browser, on a context ID 42, avoiding collision with the default context of the page ID 0. The script change the background-color of each link to yellow.

How to call C++ from JS?

In this case, we need the QWebChannel mechanism to register C++ objects into JavaScript.

First, let create the C++ interface, callable from JS (in DEFINITION):

class JsInterface: public QObject
{
    Q_OBJECT
public:
    /// Log, for debugging
    Q_INVOKABLE void log(const QString& str) const
    {
        qDebug() << "LOG from JS: " << str;
    }
};
#include "main.moc"

Explanation: This code declare and define a QObject class with a simple log function inside. It is important to declare the function Q_INVOKABLE otherwise JavaScript can not find it!. As the declaration is inside the same file as the rest of the code, we include the auto-moc file from QT after (it is main.moc because my file is main.cpp).

Create a function in DEFINITION which return the JavaScript QWebChannel.js content. The content of QWebChannel.js can be found in your QT library (./5.12.2/Src/qtwebchannel/examples/webchannel/shared/qwebchannel.js or ./Examples/Qt-5.12.2/webchannel/shared/qwebchannel.js). You are free to load this directly in your page.

In DECLARATION section, append:

QString qWebChannelJs()
{
    return R"DELIMITER(
    // COPY HERE ALL THE FILE
    )DELIMITER";
}

And we inject it in our code (Append it to TEST CODE HERE section):

browser.page()->runJavaScript(qWebChannelJs(), 42);

We need to setup the QWebChannel in C++ side (SETUP section):

QWebChannel channel;
JsInterface jsInterface;
browser.page()->setWebChannel(&channel, 42);
channel.registerObject(QString("JsInterface"), &jsInterface);

Explanation: We create a channel, the JsInterface object and register them into the browser. We need to use the same context id 42 (but could be another other number between 0 and 255).

Finally, in our JS code, we access the channel and call the function of the interface (append to TEST CODE section):

QString code2 = QStringLiteral(
R"DELIM(
                   
window.webChannel = new QWebChannel(qt.webChannelTransport, function( channel)
{
    var cpp = channel.objects.JsInterface;
    cpp.log("Hello from JavaScript");
});
                   
)DELIM");
browser.page()->runJavaScript(code2, 42);

Considerations

It is worth to mention that any call from C++ to JavaScript or from JavaScript to C++ goes through an Inter-Process-Communication (IPC) which is asynchronous. This means that runJavaScript returns before the JavaScript is executed, and that JavaScript returns before the C++ logis executed.

The JsInterface object is created once and must be registerObject into the QWebChannel before setting the channel to the page (setWebChannel). Then, every time a page is loaded, the js API is loaded (runJavaScript(qWebChannelJs())) and the channel is set to the page. If you register the JsInterface after the channel is set, you will get the following log message:

Registered new object after initialization, existing clients won't be notified!

Full code

#include <QApplication>
#include <QDebug>
#include <QWebEngineView>
#include <QWebChannel>

QString qWebChannelJs()
{
    return R"DELIMITER(
        // TODO INSERT JS code here
    )DELIMITER";
}

class JsInterface: public QObject
{
    Q_OBJECT
public:
    /// Log, for debugging
    Q_INVOKABLE void log(const QString& str) const
    {
        qDebug() << "LOG from JS: " << str;
    }
};
#include "main.moc"

auto main( int argn, char* argv[] )-> int
{
    QApplication app(argn, argv);
    QWebEngineView browser;
    browser.resize(QSize(800,600));
    browser.show();
    browser.load(QUrl("http://www.wikipedia.org"));
    
    // .. SETUP HERE
    QWebChannel channel;
    JsInterface jsInterface;
    browser.page()->setWebChannel(&channel, 42);
    channel.registerObject(QString("JsInterface"), &jsInterface);
    
    QObject::connect(&browser, &QWebEngineView::loadFinished, [&browser](bool ok)
    { 
        qDebug()<<"Load Finished " << ok;
        
        // TEST CODE HERE
        QString code = QStringLiteral(
        R"DELIM(
        
        var links = document.getElementsByTagName('a');
        for ( var i=0; i<links.length; ++i)
        {
            links[i].style.backgroundColor = 'yellow';
        };
                           
        )DELIM");
        browser.page()->runJavaScript(code, 42);
        
        browser.page()->runJavaScript(qWebChannelJs(), 42);
        
        QString code2 = QStringLiteral(
        R"DELIM(                   
        window.webChannel = new QWebChannel(qt.webChannelTransport, function( channel)
        {
            var cpp = channel.objects.JsInterface;
            cpp.log("Hello from JavaScript");
        });
                           
        )DELIM");
        browser.page()->runJavaScript(code2, 42);
    });
    
    return app.exec();
}

Related topics:

How to setup QWebChannel JS API for use in a QWebEngineView?

External documentation:

https://doc.qt.io/qt-5/qwebengineview.html
https://doc.qt.io/qt-5/qwebchannel.html
https://doc.qt.io/qt-5/qtwebengine-webenginewidgets-contentmanipulation-example.html

2
On

In Qt5.6, if you want to make C++ part and JavaScript to communicate, the only way to do it is using QWebChannel on a QWebEngineView, as you stated. You do it this way in the .cpp file:

m_pView = new QWebEngineView(this);
QWebChannel * channel = new QWebChannel(page);
m_pView->page()->setWebChannel(channel);
channel->registerObject(QString("TheNameOfTheObjectUsed"), this);

Here, you just say that you register an object named TheNameOfTheObjectUsed that will be available on the JS side. Now, this is the part of code to use in the JS side :

new QWebChannel(qt.webChannelTransport, function (channel) {
            // now you retrieve your object
            var JSobject = channel.objects.TheNameOfTheObjectUsed;
        });

Now, if you want to retrieve some properties of the class in the JS side, you need to have a method on the C++ side which returns a string, an integer, a long... This is what it looks like on the C++ side, in your .h:

Q_INVOKABLE int getInt();
Q_PROPERTY(int myIntInCppSide READ getInt);

And now, you get the int like this on the JS side :

var myIntInJSside= JSobject.myIntInCppSide;

This is a very simple explanation, and I recommend you to watch this video which was very useful to me. Also, you might want to read more about the JavaScript API provided by QWebChannel, as well as the documentation about QWebChannel.

Hope that helps!