From 65b831c21afa8540bf5414191988f41bedfcb409 Mon Sep 17 00:00:00 2001 From: Richard Weickelt Date: Tue, 26 Jun 2018 17:35:06 +0200 Subject: Add QJSEngine::throwError() method to report run-time errors It is quite common in JavaScript to use exceptions for error handling, but there was no way to generate an exception from C++ context, i.e. when the JS run-time invoked a C++ method or a slot. This patch adds an naive way to report run-time errors to QJSEngine from CPP context. The user may set a custom error message, but the location points always to the caller context in JavaScript. [ChangeLog][QtQml][QJSEngine] Added API to throw run-time errors. Task-number: QTBUG-39041 Change-Id: If59627b83d50351eb225adde63187fc251aa349e Reviewed-by: Simon Hausmann --- src/qml/jsapi/qjsengine.cpp | 68 ++++++++++++++++++++++++++++++ src/qml/jsapi/qjsengine.h | 2 + tests/auto/qml/qjsengine/tst_qjsengine.cpp | 35 +++++++++++++++ 3 files changed, 105 insertions(+) diff --git a/src/qml/jsapi/qjsengine.cpp b/src/qml/jsapi/qjsengine.cpp index ebc35a8fd7..846ceeea9c 100644 --- a/src/qml/jsapi/qjsengine.cpp +++ b/src/qml/jsapi/qjsengine.cpp @@ -738,6 +738,74 @@ bool QJSEngine::convertV2(const QJSValue &value, int type, void *ptr) \sa toScriptValue() */ +/*! + Throws a run-time error (exception) with the given \a message. + + This method is the C++ counterpart of a \c throw() expression in + JavaScript. It enables C++ code to report run-time errors to QJSEngine. + Therefore it should only be called from C++ code that was invoked by a + JavaScript function through QJSEngine. + + When returning from C++, the engine will interrupt the normal flow of + execution and call the the next pre-registered exception handler with + an error object that contains the given \a message. The error object + will point to the location of the top-most context on the JavaScript + caller stack; specifically, it will have properties \c lineNumber, + \c fileName and \c stack. These properties are described in + \l{Script Exceptions}. + + In the following example a C++ method in \e FileAccess.cpp throws an error + in \e qmlFile.qml at the position where \c readFileAsText() is called: + + \code + // qmlFile.qml + function someFunction() { + ... + var text = FileAccess.readFileAsText("/path/to/file.txt"); + } + \endcode + + \code + // FileAccess.cpp + // Assuming that FileAccess is a QObject-derived class that has been + // registered as a singleton type and provides an invokable method + // readFileAsText() + + QJSValue FileAccess::readFileAsText(const QString & filePath) { + QFile file(filePath); + + if (!file.open(QIODevice::ReadOnly)) { + jsEngine->throwError(file.errorString()); + return QString(); + } + + ... + return content; + } + \endcode + + It is also possible to catch the thrown error in JavaScript: + \code + // qmlFile.qml + function someFunction() { + ... + var text; + try { + text = FileAccess.readFileAsText("/path/to/file.txt"); + } catch (error) { + console.warn("In " + error.fileName + ":" + "error.lineNumber" + + ": " + error.message); + } + } + \endcode + + \since Qt 5.12 + \sa {Script Exceptions} +*/ +void QJSEngine::throwError(const QString &message) +{ + m_v4Engine->throwError(message); +} QJSEnginePrivate *QJSEnginePrivate::get(QV4::ExecutionEngine *e) { diff --git a/src/qml/jsapi/qjsengine.h b/src/qml/jsapi/qjsengine.h index 3ba2b52e89..36a3e475f2 100644 --- a/src/qml/jsapi/qjsengine.h +++ b/src/qml/jsapi/qjsengine.h @@ -111,6 +111,8 @@ public: QV4::ExecutionEngine *handle() const { return m_v4Engine; } + void throwError(const QString &message); + private: QJSValue create(int type, const void *ptr); diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp index 796d9e9c34..c20937f9a1 100644 --- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -219,6 +219,11 @@ private slots: void protoChanges_QTBUG68369(); void multilineStrings(); + void throwError(); + +public: + Q_INVOKABLE QJSValue throwingCppMethod(); + signals: void testSignal(); }; @@ -4291,6 +4296,36 @@ void tst_QJSEngine::multilineStrings() } +void tst_QJSEngine::throwError() +{ + QJSEngine engine; + QJSValue wrappedThis = engine.newQObject(this); + QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); + engine.globalObject().setProperty("testCase", wrappedThis); + + QJSValue result = engine.evaluate( + "function test(){\n" + "try {\n" + " return testCase.throwingCppMethod();\n" + "} catch (error) {\n" + " return error;\n" + "}\n" + "return \"not reached!\";\n" + "}\n" + "test();" + ); + QVERIFY(result.isError()); + QCOMPARE(result.property("lineNumber").toString(), "3"); + QCOMPARE(result.property("message").toString(), "blub"); + QVERIFY(!result.property("stack").isUndefined()); +} + +QJSValue tst_QJSEngine::throwingCppMethod() +{ + qjsEngine(this)->throwError("blub"); + return QJSValue(47); +} + QTEST_MAIN(tst_QJSEngine) #include "tst_qjsengine.moc" -- cgit v1.2.3