aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/qml/jsapi/qjsengine.cpp49
-rw-r--r--src/qml/jsapi/qjsengine.h2
-rw-r--r--tests/auto/qml/qjsengine/qjsengine.pro1
-rw-r--r--tests/auto/qml/qjsengine/testmodule.mjs6
-rw-r--r--tests/auto/qml/qjsengine/tst_qjsengine.cpp51
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"