diff options
-rw-r--r-- | src/qml/jsapi/qjsengine.cpp | 49 | ||||
-rw-r--r-- | src/qml/jsapi/qjsengine.h | 2 | ||||
-rw-r--r-- | tests/auto/qml/qjsengine/qjsengine.pro | 1 | ||||
-rw-r--r-- | tests/auto/qml/qjsengine/testmodule.mjs | 6 | ||||
-rw-r--r-- | tests/auto/qml/qjsengine/tst_qjsengine.cpp | 51 |
5 files changed, 109 insertions, 0 deletions
diff --git a/src/qml/jsapi/qjsengine.cpp b/src/qml/jsapi/qjsengine.cpp index 5fa81ccc2a..d20f7eb97c 100644 --- a/src/qml/jsapi/qjsengine.cpp +++ b/src/qml/jsapi/qjsengine.cpp @@ -52,6 +52,7 @@ #include <private/qqmldebugconnector_p.h> #include <private/qv4qobjectwrapper_p.h> #include <private/qv4stackframe_p.h> +#include <private/qv4module_p.h> #include <QtCore/qdatetime.h> #include <QtCore/qmetaobject.h> @@ -481,6 +482,54 @@ QJSValue QJSEngine::evaluate(const QString& program, const QString& fileName, in return retval; } +static QUrl moduleUrlForFileName(const QString &fileName) +{ + QString absolutePath = QFileInfo(fileName).canonicalFilePath(); + if (!absolutePath.startsWith(QLatin1Char(':'))) + return QUrl::fromLocalFile(absolutePath); + + absolutePath.remove(0, 1); + QUrl url; + url.setPath(absolutePath); + url.setScheme(QLatin1String("qrc")); + return url; +} + +/*! + Imports the module located at \a fileName and returns a module namespace object that + contains all exported variables, constants and functions as properties. + + If this is the first time the module is imported in the engine, the file is loaded + from the specified location in either the local file system or the Qt resource system + and evaluated as an ECMAScript module. The file is expected to be encoded in UTF-8 text. + + Subsequent imports of the same module will return the previously imported instance. Modules + are singletons and remain around until the engine is destroyed. + + The specified \a fileName will internally be normalized using \a QFileInfo::canonicalFilePath(). + That means that multiple imports of the same file on disk using different relative paths will + load the file only once. + + \note If an exception is thrown during the loading of the module, the return value + will be the exception (typically an \c{Error} object; see QJSValue::isError()). + + \since 5.12 + */ +QJSValue QJSEngine::importModule(const QString &fileName) +{ + const QUrl url = moduleUrlForFileName(fileName); + auto moduleUnit = m_v4Engine->loadModule(url); + if (m_v4Engine->hasException) + return QJSValue(m_v4Engine, m_v4Engine->catchException()); + + QV4::Scope scope(m_v4Engine); + QV4::Scoped<QV4::Module> moduleNamespace(scope, moduleUnit->instantiate(m_v4Engine)); + if (m_v4Engine->hasException) + return QJSValue(m_v4Engine, m_v4Engine->catchException()); + moduleUnit->evaluate(); + return QJSValue(m_v4Engine, moduleNamespace->asReturnedValue()); +} + /*! Creates a JavaScript object of class Object. diff --git a/src/qml/jsapi/qjsengine.h b/src/qml/jsapi/qjsengine.h index 36a3e475f2..5c8613ffd6 100644 --- a/src/qml/jsapi/qjsengine.h +++ b/src/qml/jsapi/qjsengine.h @@ -69,6 +69,8 @@ public: QJSValue evaluate(const QString &program, const QString &fileName = QString(), int lineNumber = 1); + QJSValue importModule(const QString &fileName); + QJSValue newObject(); QJSValue newArray(uint length = 0); diff --git a/tests/auto/qml/qjsengine/qjsengine.pro b/tests/auto/qml/qjsengine/qjsengine.pro index c9d78e22a0..ea4d3ea464 100644 --- a/tests/auto/qml/qjsengine/qjsengine.pro +++ b/tests/auto/qml/qjsengine/qjsengine.pro @@ -4,5 +4,6 @@ QT += qml qml-private widgets testlib gui-private macx:CONFIG -= app_bundle SOURCES += tst_qjsengine.cpp RESOURCES += qjsengine.qrc +RESOURCES += testmodule.mjs TESTDATA = script/* diff --git a/tests/auto/qml/qjsengine/testmodule.mjs b/tests/auto/qml/qjsengine/testmodule.mjs new file mode 100644 index 0000000000..df561c06a1 --- /dev/null +++ b/tests/auto/qml/qjsengine/testmodule.mjs @@ -0,0 +1,6 @@ + +export var value = 42; + +export function sideEffect() { + value = value + 1 +} diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp index 54b11c6215..927cc16271 100644 --- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -40,6 +40,7 @@ #include <stdlib.h> #include <private/qv4alloca_p.h> #include <private/qjsvalue_p.h> +#include <QScopeGuard> #ifdef Q_CC_MSVC #define NO_INLINE __declspec(noinline) @@ -223,6 +224,9 @@ private slots: void throwError(); void mathMinMax(); + void importModule(); + void importModuleRelative(); + public: Q_INVOKABLE QJSValue throwingCppMethod(); @@ -4339,6 +4343,53 @@ void tst_QJSEngine::mathMinMax() QVERIFY(QJSValuePrivate::getValue(&result)->isInteger()); } +void tst_QJSEngine::importModule() +{ + // This is just a basic test for the API. Primary test coverage is via the ES test suite. + QJSEngine engine; + QJSValue ns = engine.importModule(QStringLiteral(":/testmodule.mjs")); + QCOMPARE(ns.property("value").toInt(), 42); + ns.property("sideEffect").call(); + + // Make sure that importing a second time will return the same instance. + QJSValue secondNamespace = engine.importModule(QStringLiteral(":/testmodule.mjs")); + QCOMPARE(secondNamespace.property("value").toInt(), 43); +} + +void tst_QJSEngine::importModuleRelative() +{ + const QString oldWorkingDirectory = QDir::currentPath(); + auto workingDirectoryGuard = qScopeGuard([oldWorkingDirectory]{ + QDir::setCurrent(oldWorkingDirectory); + }); + + QTemporaryDir tempDir; + QVERIFY(tempDir.isValid()); + QDir::setCurrent(tempDir.path()); + + { + QFile f(QStringLiteral("relativemodule.mjs")); + QVERIFY(f.open(QIODevice::WriteOnly|QIODevice::Truncate)); + f.write(QByteArrayLiteral("var value = 100; export { value }; export function change() { value = value + 1 }")); + } + + QJSEngine engine; + + { + QJSValue module = engine.importModule(QStringLiteral("relativemodule.mjs")); + QVERIFY2(!module.isError(), qPrintable(module.toString())); + QCOMPARE(module.property("value").toInt(), 100); + + module.property("change").call(); + } + + { + QJSValue sameModule = engine.importModule(tempDir.filePath(QStringLiteral("relativemodule.mjs"))); + QVERIFY2(!sameModule.isError(), qPrintable(sameModule.toString())); + QCOMPARE(sameModule.property("value").toInt(), 101); + } +} + QTEST_MAIN(tst_QJSEngine) #include "tst_qjsengine.moc" |