I have the following method in one of my C++ classes (using QtWebEngine):
QString get()
{
QString result;
view->page()->runJavaScript("test();", [this](const QVariant &v)
{
result = v.toString();
});
return result;
}
It is to execute test()
JS function and return the result of this invocation.
Unfortunately, the callback is asynchronous and the program crashes. How can I make it work?
The callback is asynchronous because the JavaScript execution occurs not only in another thread but in another process. So there is no way to make it fully synchronous.
The best possible solution would be to migrate your C++ code to work asynchronously. If you can't do that, the only feasible solution is to use QEventLoop
, somewhat like this:
void ranJavaScript()
{
emit notifyRanJavaScript();
}
QString get()
{
QString result;
QEventLoop loop;
QObject::connect(this, SIGNAL(notifyRanJavaScript()), &loop, SLOT(quit()));
view->page()->runJavaScript("test();", [this](const QVariant &v)
{
result = v.toString();
this.ranJavaScript();
});
loop.exec();
return result;
}
However, note that this example is oversimplified for a real-world usage: you need to ensure the JavaScript was not ran before the event loop is started. The most proper way to do that would involve implementing a proper slot instead of a lambda + factoring out the call to view->page()->runJavaScript()
into another slot which would be called asynchronously after starting the event loop. It is a lot of glue code for such a seemingly simple task but that's what it takes. Here's an example:
MainWindow.h
#ifndef TMP_MAIN_WINDOW_H
#define TMP_MAIN_WINDOW_H
#include <QMainWindow>
#include <QVariant>
class QWebEngineView;
class QPushButton;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget * parent = 0);
QString get();
void onScriptEnded(const QVariant & data);
Q_SIGNALS:
void notifyRanJavaScript();
private Q_SLOTS:
void onButtonPressed();
void startScript();
private:
QWebEngineView * m_view;
QPushButton * m_button;
QString m_scriptResult;
};
#endif // TMP_MAIN_WINDOW_H
MainWindow.cpp
#include "MainWindow.h"
#include <QWebEngineView>
#include <QPushButton>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QMessageBox>
#include <QEventLoop>
#include <QDebug>
#include <QTimer>
MainWindow::MainWindow(QWidget * parent) :
QMainWindow(parent)
{
m_view = new QWebEngineView;
QWebEnginePage * page = new QWebEnginePage(m_view);
m_view->setPage(page);
QString html = QStringLiteral("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\""
"\"http://www.w3.org/TR/html4/strict.dtd\"><html>"
"<head><h3>head</h3>\n</head>"
"<script type=\"text/javascript\">function test() { return \"A!\"; }</script>"
"<body>text\n</body></html>");
m_view->page()->setHtml(html);
m_button = new QPushButton;
m_button->setMinimumWidth(35);
m_button->setText(QStringLiteral("Test"));
QObject::connect(m_button, SIGNAL(pressed()), this, SLOT(onButtonPressed()));
QHBoxLayout * buttonLayout = new QHBoxLayout;
buttonLayout->addWidget(m_button);
buttonLayout->addStretch();
QVBoxLayout * viewLayout = new QVBoxLayout;
viewLayout->addLayout(buttonLayout);
viewLayout->addWidget(m_view);
QWidget * widget = new QWidget(this);
widget->setLayout(viewLayout);
setCentralWidget(widget);
}
QString MainWindow::get()
{
QEventLoop loop;
QObject::connect(this, SIGNAL(notifyRanJavaScript()), &loop, SLOT(quit()));
// Schedule the slot to run in 0 seconds but not right now
QTimer::singleShot(0, this, SLOT(startScript()));
// The event loop would block until onScriptEnded slot is executed
loop.exec();
// If we got here, the script has been executed and the result was saved in m_scriptResult
return m_scriptResult;
}
void MainWindow::onScriptEnded(const QVariant & data)
{
qDebug() << QStringLiteral("Script ended: ") << data;
m_scriptResult = data.toString();
emit notifyRanJavaScript();
}
void MainWindow::onButtonPressed()
{
QString str = get();
QMessageBox::information(this, QStringLiteral("Script result"), str,
QMessageBox::StandardButton::Ok);
}
struct Functor
{
Functor(MainWindow & window) : m_window(window) {}
void operator()(const QVariant & data)
{
m_window.onScriptEnded(data);
}
MainWindow & m_window;
};
void MainWindow::startScript()
{
qDebug() << QStringLiteral("Start script");
m_view->page()->runJavaScript(QStringLiteral("test();"), Functor(*this));
}
In the kchmviewer project, I found a very easy way to wait for the result of the runJavaScript function. The following code is part of the ViewWindow class which inherits from QWebEngineView.
int ViewWindow::getScrollbarPosition()
{
QAtomicInt value = -1;
page()->runJavaScript("document.body.scrollTop", [&value](const QVariant &v)
{
qDebug( "value retrieved: %d\n", v.toInt());
value = v.toInt();
});
while (value == -1)
{
QApplication::processEvents();
}
qDebug( "scroll value %d", value.load() );
return value;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With