web-dev-qa-db-de.com

QtWebEngine - Synchrones Ausführen von JavaScript zum Lesen des Funktionsergebnisses

Ich habe die folgende Methode in einer meiner C++ - Klassen (mit QtWebEngine):

    QString get()
    {
        QString result;

        view->page()->runJavaScript("test();", [this](const QVariant &v)
            {
                result = v.toString();
            });

        return result;
    }

Es soll die Funktion test() JS ausführen und das Ergebnis dieses Aufrufs zurückgeben.

Leider ist der Rückruf asynchron und das Programm stürzt ab. Wie kann ich es zum Laufen bringen?

9
zupazt3

Der Rückruf ist asynchron, da die JavaScript-Ausführung nicht nur in einem anderen Thread, sondern auch in einem anderen process erfolgt. Es gibt also keine Möglichkeit, es vollständig synchron zu machen.

Die bestmögliche Lösung wäre, Ihren C++ - Code so zu migrieren, dass er asynchron funktioniert. Wenn Sie das nicht können, ist die einzig mögliche Lösung die Verwendung von QEventLoop, etwa so:

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

Beachten Sie jedoch, dass dieses Beispiel für eine reale Verwendung zu stark vereinfacht ist: Sie müssen sicherstellen, dass JavaScript nicht ausgeführt wurde, bevor die Ereignisschleife gestartet wird. Der geeignetste Weg, dies zu tun, wäre die Implementierung eines richtigen Slots anstelle eines Lambda +, der den Aufruf von view->page()->runJavaScript() in einen anderen Slot zerlegt, der asynchron nach aufgerufen würde, um die Ereignisschleife zu starten. Es ist eine Menge Klebercode für solch eine scheinbar einfache Aufgabe, aber das ist es, was es braucht. Hier ist ein Beispiel:

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));
}
6
Dmitry

Die Lösung von Dmitry funktioniert leider nur teilweise. In seinem Beispiel wird der Code per Knopfdruck aufgerufen. Wenn jedoch derselbe Code verschoben wird, um ausgeführt zu werden, während das Fenster geladen wird, wird der Slot für das fertige Skript (onScriptEnded) niemals aufgerufen. Um dies zu beheben, musste ich den Aufruf der Evaluierung von JS selbst (in meinem Projekt EvaluierungsJavaScript genannt) in ein anderes QTimer :: singleShot einbinden:

QTimer::singleShot(0, this, [&] { evaluateJavaScript(); });

Leider habe ich in meinem realen Projekt viele Anrufe, oft wartet eine Bewertung auf eine andere, bevor sie abgeschlossen ist. Es ist praktisch unmöglich, dies jedes Mal zu verwenden, daher suche ich immer noch nach einer Lösung.

0
Dimitar Dobrev

C++ ruft Javascript-Funktion auf

Dies ist im Vergleich zum Aufrufen der C++ - Funktion von js aus sehr unkompliziert. Verwenden Sie einfach runJavaScript und übergeben Sie die Funktion wie folgt als Parameter:

view->page()->runJavaScript("jsfun();",[this](const QVariant &v) { qDebug()<<v.toString();});

Es wird davon ausgegangen, dass der jsfun bereits auf Ihrer Webseite definiert ist. Andernfalls müssen Sie ihn im string-Parameter definieren. Der Rückgabewert wird asynchron als Parameter v an den Lambda-Ausdruck übergeben.

0
peter