QML: Make modules imported with registerModule() available in QML

So far, you could only use them from pure JavaScript programs. Also, fix
re-exporting parts of native modules.

Fixes: QTBUG-105901
Change-Id: I170017083284e6457b1aa0c6e606fd26227edae3
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Ulf Hermann 2022-08-25 11:42:23 +02:00
parent 7c56dc7e98
commit b297e8fccf
14 changed files with 283 additions and 121 deletions

View File

@ -566,20 +566,26 @@ QJSValue QJSEngine::evaluate(const QString& program, const QString& fileName, in
QJSValue QJSEngine::importModule(const QString &fileName)
{
const QUrl url = urlForFileName(QFileInfo(fileName).canonicalFilePath());
auto moduleUnit = m_v4Engine->loadModule(url);
const auto module = m_v4Engine->loadModule(url);
if (m_v4Engine->hasException)
return QJSValuePrivate::fromReturnedValue(m_v4Engine->catchException());
QV4::Scope scope(m_v4Engine);
QV4::Scoped<QV4::Module> moduleNamespace(scope, moduleUnit->instantiate(m_v4Engine));
if (m_v4Engine->hasException)
return QJSValuePrivate::fromReturnedValue(m_v4Engine->catchException());
moduleUnit->evaluate();
if (!m_v4Engine->isInterrupted.loadRelaxed())
return QJSValuePrivate::fromReturnedValue(moduleNamespace->asReturnedValue());
if (const auto compiled = module.compiled) {
QV4::Scoped<QV4::Module> moduleNamespace(scope, compiled->instantiate(m_v4Engine));
if (m_v4Engine->hasException)
return QJSValuePrivate::fromReturnedValue(m_v4Engine->catchException());
compiled->evaluate();
if (!m_v4Engine->isInterrupted.loadRelaxed())
return QJSValuePrivate::fromReturnedValue(moduleNamespace->asReturnedValue());
return QJSValuePrivate::fromReturnedValue(
m_v4Engine->newErrorObject(QStringLiteral("Interrupted"))->asReturnedValue());
}
return QJSValuePrivate::fromReturnedValue(
m_v4Engine->newErrorObject(QStringLiteral("Interrupted"))->asReturnedValue());
// If there is neither a native nor a compiled module, we should have seen an exception
Q_ASSERT(module.native);
return QJSValuePrivate::fromReturnedValue(module.native->asReturnedValue());
}
/*!
@ -609,7 +615,9 @@ QJSValue QJSEngine::importModule(const QString &fileName)
*/
bool QJSEngine::registerModule(const QString &moduleName, const QJSValue &value)
{
m_v4Engine->registerModule(moduleName, value);
QV4::Scope scope(m_v4Engine);
QV4::ScopedValue v4Value(scope, QJSValuePrivate::asReturnedValue(&value));
m_v4Engine->registerNativeModule(QUrl(moduleName), v4Value);
if (m_v4Engine->hasException)
return false;
return true;

View File

@ -2035,7 +2035,7 @@ QQmlRefPointer<ExecutableCompilationUnit> ExecutionEngine::compileModule(
return ExecutableCompilationUnit::create(std::move(unit));
}
void ExecutionEngine::injectModule(const QQmlRefPointer<ExecutableCompilationUnit> &moduleUnit)
void ExecutionEngine::injectCompiledModule(const QQmlRefPointer<ExecutableCompilationUnit> &moduleUnit)
{
// Injection can happen from the QML type loader thread for example, but instantiation and
// evaluation must be limited to the ExecutionEngine's thread.
@ -2043,52 +2043,59 @@ void ExecutionEngine::injectModule(const QQmlRefPointer<ExecutableCompilationUni
modules.insert(moduleUnit->finalUrl(), moduleUnit);
}
QQmlRefPointer<ExecutableCompilationUnit> ExecutionEngine::moduleForUrl(const QUrl &_url, const ExecutableCompilationUnit *referrer) const
ExecutionEngine::Module ExecutionEngine::moduleForUrl(
const QUrl &url, const ExecutableCompilationUnit *referrer) const
{
QUrl url = QQmlTypeLoader::normalize(_url);
if (referrer)
url = referrer->finalUrl().resolved(url);
QMutexLocker moduleGuard(&moduleMutex);
auto existingModule = modules.find(url);
const auto nativeModule = nativeModules.find(url);
if (nativeModule != nativeModules.end())
return Module { nullptr, *nativeModule };
const QUrl resolved = referrer
? referrer->finalUrl().resolved(QQmlTypeLoader::normalize(url))
: QQmlTypeLoader::normalize(url);
auto existingModule = modules.find(resolved);
if (existingModule == modules.end())
return nullptr;
return *existingModule;
return Module { nullptr, nullptr };
return Module { *existingModule, nullptr };
}
QQmlRefPointer<ExecutableCompilationUnit> ExecutionEngine::loadModule(const QUrl &_url, const ExecutableCompilationUnit *referrer)
ExecutionEngine::Module ExecutionEngine::loadModule(const QUrl &url, const ExecutableCompilationUnit *referrer)
{
QUrl url = QQmlTypeLoader::normalize(_url);
if (referrer)
url = referrer->finalUrl().resolved(url);
QMutexLocker moduleGuard(&moduleMutex);
auto existingModule = modules.find(url);
const auto nativeModule = nativeModules.find(url);
if (nativeModule != nativeModules.end())
return Module { nullptr, *nativeModule };
const QUrl resolved = referrer
? referrer->finalUrl().resolved(QQmlTypeLoader::normalize(url))
: QQmlTypeLoader::normalize(url);
auto existingModule = modules.find(resolved);
if (existingModule != modules.end())
return *existingModule;
return Module { *existingModule, nullptr };
moduleGuard.unlock();
auto newModule = compileModule(url);
auto newModule = compileModule(resolved);
if (newModule) {
moduleGuard.relock();
modules.insert(url, newModule);
modules.insert(resolved, newModule);
}
return newModule;
return Module { newModule, nullptr };
}
void ExecutionEngine::registerModule(const QString &_name, const QJSValue &module)
QV4::Value *ExecutionEngine::registerNativeModule(const QUrl &url, const QV4::Value &module)
{
const QUrl url(_name);
QMutexLocker moduleGuard(&moduleMutex);
const auto existingModule = nativeModules.find(url);
if (existingModule != nativeModules.end())
return;
return nullptr;
QV4::Value* val = this->memoryManager->m_persistentValues->allocate();
*val = QJSValuePrivate::asReturnedValue(&module);
QV4::Value *val = this->memoryManager->m_persistentValues->allocate();
*val = module.asReturnedValue();
nativeModules.insert(url, val);
return val;
}
bool ExecutionEngine::diskCacheEnabled() const

View File

@ -714,19 +714,18 @@ public:
QQmlRefPointer<ExecutableCompilationUnit> compileModule(
const QUrl &url, const QString &sourceCode, const QDateTime &sourceTimeStamp);
mutable QMutex moduleMutex;
QHash<QUrl, QQmlRefPointer<ExecutableCompilationUnit>> modules;
void injectCompiledModule(const QQmlRefPointer<ExecutableCompilationUnit> &moduleUnit);
QV4::Value *registerNativeModule(const QUrl &url, const QV4::Value &module);
// QV4::PersistentValue would be preferred, but using QHash will create copies,
// and QV4::PersistentValue doesn't like creating copies.
// Instead, we allocate a raw pointer using the same manual memory management
// technique in QV4::PersistentValue.
QHash<QUrl, QV4::Value*> nativeModules;
struct Module {
QQmlRefPointer<ExecutableCompilationUnit> compiled;
void injectModule(const QQmlRefPointer<ExecutableCompilationUnit> &moduleUnit);
QQmlRefPointer<ExecutableCompilationUnit> moduleForUrl(const QUrl &_url, const ExecutableCompilationUnit *referrer = nullptr) const;
QQmlRefPointer<ExecutableCompilationUnit> loadModule(const QUrl &_url, const ExecutableCompilationUnit *referrer = nullptr);
void registerModule(const QString &name, const QJSValue &module);
// We can pass a raw value pointer here, but nowhere else. See below.
Value *native = nullptr;
};
Module moduleForUrl(const QUrl &_url, const ExecutableCompilationUnit *referrer = nullptr) const;
Module loadModule(const QUrl &_url, const ExecutableCompilationUnit *referrer = nullptr);
bool diskCacheEnabled() const;
@ -767,6 +766,15 @@ private:
QHash<QString, quint32> m_consoleCount;
QVector<Deletable *> m_extensionData;
mutable QMutex moduleMutex;
QHash<QUrl, QQmlRefPointer<ExecutableCompilationUnit>> modules;
// QV4::PersistentValue would be preferred, but using QHash will create copies,
// and QV4::PersistentValue doesn't like creating copies.
// Instead, we allocate a raw pointer using the same manual memory management
// technique in QV4::PersistentValue.
QHash<QUrl, Value *> nativeModules;
};
#define CHECK_STACK_LIMITS(v4) if ((v4)->checkStackLimits()) return Encode::undefined(); \

View File

@ -22,6 +22,7 @@
#include <private/qqmltypewrapper_p.h>
#include <private/inlinecomponentutils_p.h>
#include <private/qv4resolvedtypereference_p.h>
#include <private/qv4objectiterator_p.h>
#include <QtQml/qqmlfile.h>
#include <QtQml/qqmlpropertymap.h>
@ -535,13 +536,11 @@ Heap::Module *ExecutableCompilationUnit::instantiate(ExecutionEngine *engine)
for (const QString &request: moduleRequests()) {
const QUrl url(request);
if (engine->nativeModules.contains(url))
continue;
auto dependentModuleUnit = engine->loadModule(url, this);
const auto dependentModuleUnit = engine->loadModule(url, this);
if (engine->hasException)
return nullptr;
dependentModuleUnit->instantiate(engine);
if (dependentModuleUnit.compiled)
dependentModuleUnit.compiled->instantiate(engine);
}
ScopedString importName(scope);
@ -554,12 +553,22 @@ Heap::Module *ExecutableCompilationUnit::instantiate(ExecutionEngine *engine)
for (uint i = 0; i < importCount; ++i) {
const CompiledData::ImportEntry &entry = data->importEntryTable()[i];
QUrl url = urlAt(entry.moduleRequest);
const auto nativeModule = engine->nativeModules.find(url);
if (nativeModule != engine->nativeModules.end()) {
importName = runtimeStrings[entry.importName];
const QString name = importName->toQString();
importName = runtimeStrings[entry.importName];
QV4::Value *value = nativeModule.value();
const auto module = engine->loadModule(url, this);
if (module.compiled) {
const Value *valuePtr = module.compiled->resolveExport(importName);
if (!valuePtr) {
QString referenceErrorMessage = QStringLiteral("Unable to resolve import reference ");
referenceErrorMessage += importName->toQString();
engine->throwReferenceError(
referenceErrorMessage, fileName(),
entry.location.line(), entry.location.column());
return nullptr;
}
imports[i] = valuePtr;
} else if (Value *value = module.native) {
const QString name = importName->toQString();
if (value->isNullOrUndefined()) {
QString errorMessage = name;
errorMessage += QStringLiteral(" from ");
@ -573,9 +582,9 @@ Heap::Module *ExecutableCompilationUnit::instantiate(ExecutionEngine *engine)
imports[i] = value;
} else {
url.setFragment(name);
auto fragment = engine->nativeModules.find(url);
if (fragment != engine->nativeModules.end()) {
imports[i] = fragment.value();
const auto fragment = engine->moduleForUrl(url, this);
if (fragment.native) {
imports[i] = fragment.native;
} else {
Scope scope(this->engine);
ScopedObject o(scope, value);
@ -593,42 +602,37 @@ Heap::Module *ExecutableCompilationUnit::instantiate(ExecutionEngine *engine)
const ScopedPropertyKey key(scope, scope.engine->identifierTable->asPropertyKey(name));
const ScopedValue result(scope, o->get(key));
Value *valuePtr = engine->memoryManager->m_persistentValues->allocate();
*valuePtr = result->asReturnedValue();
imports[i] = valuePtr;
engine->nativeModules.insert(url, valuePtr);
imports[i] = engine->registerNativeModule(url, result);
}
}
} else {
auto dependentModuleUnit = engine->loadModule(url, this);
importName = runtimeStrings[entry.importName];
const Value *valuePtr = dependentModuleUnit->resolveExport(importName);
if (!valuePtr) {
QString referenceErrorMessage = QStringLiteral("Unable to resolve import reference ");
referenceErrorMessage += importName->toQString();
engine->throwReferenceError(
referenceErrorMessage, fileName(),
entry.location.line(), entry.location.column());
return nullptr;
}
imports[i] = valuePtr;
}
}
const auto throwReferenceError = [&](const CompiledData::ExportEntry &entry, const QString &importName) {
QString referenceErrorMessage = QStringLiteral("Unable to resolve re-export reference ");
referenceErrorMessage += importName;
engine->throwReferenceError(
referenceErrorMessage, fileName(),
entry.location.line(), entry.location.column());
};
for (uint i = 0; i < data->indirectExportEntryTableSize; ++i) {
const CompiledData::ExportEntry &entry = data->indirectExportEntryTable()[i];
auto dependentModuleUnit = engine->loadModule(urlAt(entry.moduleRequest), this);
if (!dependentModuleUnit)
return nullptr;
auto dependentModule = engine->loadModule(urlAt(entry.moduleRequest), this);
ScopedString importName(scope, runtimeStrings[entry.importName]);
if (!dependentModuleUnit->resolveExport(importName)) {
QString referenceErrorMessage = QStringLiteral("Unable to resolve re-export reference ");
referenceErrorMessage += importName->toQString();
engine->throwReferenceError(
referenceErrorMessage, fileName(),
entry.location.line(), entry.location.column());
return nullptr;
if (const auto dependentModuleUnit = dependentModule.compiled) {
if (!dependentModuleUnit->resolveExport(importName)) {
throwReferenceError(entry, importName->toQString());
return nullptr;
}
} else if (const auto native = dependentModule.native) {
ScopedObject o(scope, native);
const ScopedPropertyKey key(scope, scope.engine->identifierTable->asPropertyKey(importName));
const ScopedValue result(scope, o->get(key));
if (result->isUndefined()) {
throwReferenceError(entry, importName->toQString());
return nullptr;
}
}
}
@ -665,13 +669,31 @@ const Value *ExecutableCompilationUnit::resolveExportRecursively(
if (auto indirectExport = lookupNameInExportTable(
data->indirectExportEntryTable(), data->indirectExportEntryTableSize, exportName)) {
auto dependentModuleUnit = engine->loadModule(urlAt(indirectExport->moduleRequest), this);
if (!dependentModuleUnit)
return nullptr;
QUrl request = urlAt(indirectExport->moduleRequest);
auto dependentModule = engine->loadModule(request, this);
ScopedString importName(scope, runtimeStrings[indirectExport->importName]);
return dependentModuleUnit->resolveExportRecursively(importName, resolveSet);
}
if (dependentModule.compiled) {
return dependentModule.compiled->resolveExportRecursively(importName, resolveSet);
} else if (dependentModule.native) {
if (exportName->toQString() == QLatin1String("*"))
return dependentModule.native;
if (exportName->toQString() == QLatin1String("default"))
return nullptr;
request.setFragment(importName->toQString());
const auto fragment = engine->moduleForUrl(request);
if (fragment.native)
return fragment.native;
ScopedObject o(scope, dependentModule.native);
if (o)
return engine->registerNativeModule(request, o->get(importName));
return nullptr;
} else {
return nullptr;
}
}
if (exportName->toQString() == QLatin1String("default"))
return nullptr;
@ -680,11 +702,28 @@ const Value *ExecutableCompilationUnit::resolveExportRecursively(
for (uint i = 0; i < data->starExportEntryTableSize; ++i) {
const CompiledData::ExportEntry &entry = data->starExportEntryTable()[i];
auto dependentModuleUnit = engine->loadModule(urlAt(entry.moduleRequest), this);
if (!dependentModuleUnit)
return nullptr;
QUrl request = urlAt(entry.moduleRequest);
auto dependentModule = engine->loadModule(request, this);
const Value *resolution = nullptr;
if (dependentModule.compiled) {
resolution = dependentModule.compiled->resolveExportRecursively(
exportName, resolveSet);
} else if (dependentModule.native) {
if (exportName->toQString() == QLatin1String("*")) {
resolution = dependentModule.native;
} else if (exportName->toQString() != QLatin1String("default")) {
request.setFragment(exportName->toQString());
const auto fragment = engine->moduleForUrl(request);
if (fragment.native) {
resolution = fragment.native;
} else {
ScopedObject o(scope, dependentModule.native);
if (o)
resolution = engine->registerNativeModule(request, o->get(exportName));
}
}
}
const Value *resolution = dependentModuleUnit->resolveExportRecursively(exportName, resolveSet);
// ### handle ambiguous
if (resolution) {
if (!starResolution) {
@ -737,10 +776,21 @@ void ExecutableCompilationUnit::getExportedNamesRecursively(
for (uint i = 0; i < data->starExportEntryTableSize; ++i) {
const CompiledData::ExportEntry &entry = data->starExportEntryTable()[i];
auto dependentModuleUnit = engine->loadModule(urlAt(entry.moduleRequest), this);
if (!dependentModuleUnit)
return;
dependentModuleUnit->getExportedNamesRecursively(names, exportNameSet, /*includeDefaultExport*/false);
auto dependentModule = engine->loadModule(urlAt(entry.moduleRequest), this);
if (dependentModule.compiled) {
dependentModule.compiled->getExportedNamesRecursively(
names, exportNameSet, /*includeDefaultExport*/false);
} else if (dependentModule.native) {
Scope scope(engine);
ScopedObject o(scope, dependentModule.native);
ObjectIterator iterator(scope, o, ObjectIterator::EnumerableOnly);
while (true) {
ScopedValue val(scope, iterator.nextPropertyNameAsString());
if (val->isNull())
break;
append(val->toQString());
}
}
}
}
@ -754,12 +804,15 @@ void ExecutableCompilationUnit::evaluate()
void ExecutableCompilationUnit::evaluateModuleRequests()
{
for (const QString &request: moduleRequests()) {
if (engine->nativeModules.contains(QUrl(request)))
auto dependentModule = engine->loadModule(QUrl(request), this);
if (dependentModule.native)
continue;
auto dependentModuleUnit = engine->loadModule(QUrl(request), this);
if (engine->hasException)
return;
dependentModuleUnit->evaluate();
Q_ASSERT(dependentModule.compiled);
dependentModule.compiled->evaluate();
if (engine->hasException)
return;
}

View File

@ -213,10 +213,11 @@ void QQmlScriptBlob::initializeFromCompilationUnit(const QQmlRefPointer<QV4::Exe
auto *v4 = QQmlEnginePrivate::getV4Engine(typeLoader()->engine());
v4->injectModule(unit);
v4->injectCompiledModule(unit);
for (const QString &request: unit->moduleRequests()) {
if (v4->moduleForUrl(QUrl(request), unit.data()))
const auto module = v4->moduleForUrl(QUrl(request), unit.data());
if (module.compiled || module.native)
continue;
const QUrl absoluteRequest = unit->finalUrl().resolved(QUrl(request));
@ -226,4 +227,23 @@ void QQmlScriptBlob::initializeFromCompilationUnit(const QQmlRefPointer<QV4::Exe
}
}
/*!
\internal
This initializes a dummy script blob from a "native" ECMAScript module.
Native modules are just JavaScript values, possibly objects with members.
\sa QJSEngine::registerModule()
*/
void QQmlScriptBlob::initializeFromNative(const QV4::Value &value)
{
Q_ASSERT(!m_scriptData);
m_scriptData.adopt(new QQmlScriptData());
m_scriptData->url = finalUrl();
m_scriptData->urlString = finalUrlString();
m_scriptData->m_loaded = true;
m_scriptData->m_value.set(QQmlEnginePrivate::getV4Engine(typeLoader()->engine()), value);
m_importCache->setBaseUrl(finalUrl(), finalUrlString());
}
QT_END_NAMESPACE

View File

@ -50,6 +50,7 @@ protected:
private:
void scriptImported(const QQmlRefPointer<QQmlScriptBlob> &blob, const QV4::CompiledData::Location &location, const QString &qualifier, const QString &nameSpace) override;
void initializeFromCompilationUnit(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &unit);
void initializeFromNative(const QV4::Value &value);
QList<ScriptReference> m_scripts;
QQmlRefPointer<QQmlScriptData> m_scriptData;

View File

@ -23,7 +23,7 @@ QQmlRefPointer<QQmlContextData> QQmlScriptData::qmlContextDataForContext(
{
Q_ASSERT(parentQmlContextData && parentQmlContextData->engine());
if (m_precompiledScript->isESModule())
if (!m_precompiledScript || m_precompiledScript->isESModule())
return nullptr;
QQmlRefPointer<QQmlContextData> qmlContextData = m_precompiledScript->isSharedLibrary()

View File

@ -530,8 +530,16 @@ bool QQmlTypeLoader::Blob::updateQmldir(const QQmlRefPointer<QQmlQmldirData> &da
bool QQmlTypeLoader::Blob::addScriptImport(const QQmlTypeLoader::Blob::PendingImportPtr &import)
{
QUrl scriptUrl = finalUrl().resolved(QUrl(import->uri));
QQmlRefPointer<QQmlScriptBlob> blob = typeLoader()->getScript(scriptUrl);
const QUrl url(import->uri);
const auto module = m_typeLoader->engine()->handle()->moduleForUrl(url);
QQmlRefPointer<QQmlScriptBlob> blob;
if (module.native) {
blob.adopt(new QQmlScriptBlob(url, m_typeLoader));
blob->initializeFromNative(*module.native);
blob->tryDone();
} else {
blob = typeLoader()->getScript(finalUrl().resolved(url));
}
addDependency(blob.data());
scriptImported(blob, import->location, import->qualifier, QString());
return true;

View File

@ -230,10 +230,12 @@ void QQuickWorkerScriptEnginePrivate::processLoad(int id, const QUrl &url)
script->source = url;
if (fileName.endsWith(QLatin1String(".mjs"))) {
auto moduleUnit = engine->loadModule(url);
if (moduleUnit) {
if (moduleUnit->instantiate(engine))
moduleUnit->evaluate();
auto module = engine->loadModule(url);
if (module.compiled) {
if (module.compiled->instantiate(engine))
module.compiled->evaluate();
} else if (module.native) {
// Nothing to do. There is no global code in a native module.
} else {
engine->throwError(QStringLiteral("Could not load module file"));
}

View File

@ -503,7 +503,7 @@ static TestCase::Result executeTest(const QByteArray &data, bool runAsModule = f
module = vm.compileModule(url.toString(), QString::fromUtf8(content.constData(), content.length()), QFileInfo(f).lastModified());
if (vm.hasException)
break;
vm.injectModule(module);
vm.injectCompiledModule(module);
} else {
vm.throwError(QStringLiteral("Could not load module"));
break;
@ -511,16 +511,16 @@ static TestCase::Result executeTest(const QByteArray &data, bool runAsModule = f
for (const QString &request: module->moduleRequests()) {
const QUrl absoluteRequest = module->finalUrl().resolved(QUrl(request));
if (!vm.modules.contains(absoluteRequest))
const auto module = vm.moduleForUrl(absoluteRequest);
if (module.native == nullptr && module.compiled == nullptr)
modulesToLoad << absoluteRequest;
}
}
if (!vm.hasException) {
if (auto rootModuleUnit = vm.loadModule(rootModuleUrl)) {
if (rootModuleUnit->instantiate(&vm))
rootModuleUnit->evaluate();
}
const auto rootModule = vm.loadModule(rootModuleUrl);
if (rootModule.compiled && rootModule.compiled->instantiate(&vm))
rootModule.compiled->evaluate();
}
} else {
QV4::ScopedContext ctx(scope, vm.rootContext());

View File

@ -0,0 +1,16 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import { name } from "info.mjs";
export function getName()
{
return name;
}
export function func() {
return "Hello World"
}
export {name as foo} from "info.mjs";
export * from "info.mjs";

View File

@ -0,0 +1,15 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import QtQml
import "info.mjs" as Info
import "nativeModuleImport.mjs" as Test
QtObject {
property string a: Test.func()
property string b: Test.getName()
property string c: Info.name
property string d: Test.name
property string e: Test.foo
}

View File

@ -70,6 +70,7 @@ private slots:
void stringToColor();
void qobjectToString();
void qtNamespaceInQtObject();
void nativeModuleImport();
public slots:
QObject *createAQObjectForOwnershipTest ()
@ -1615,6 +1616,27 @@ void tst_qqmlengine::qtNamespaceInQtObject()
QVERIFY(qtObject.hasProperty(QStringLiteral("objectName")));
}
void tst_qqmlengine::nativeModuleImport()
{
QQmlEngine engine;
QJSValue name("TheName");
QJSValue obj = engine.newObject();
obj.setProperty("name", name);
engine.registerModule("info.mjs", obj);
QQmlComponent c(&engine, testFileUrl("nativeModuleImport.qml"));
QVERIFY2(c.isReady(), qPrintable(c.errorString()));
QScopedPointer<QObject> o(c.create());
QCOMPARE(o->property("a").toString(), QStringLiteral("Hello World"));
QCOMPARE(o->property("b").toString(), QStringLiteral("TheName"));
QCOMPARE(o->property("c").toString(), QStringLiteral("TheName"));
QCOMPARE(o->property("d").toString(), QStringLiteral("TheName"));
QCOMPARE(o->property("e").toString(), QStringLiteral("TheName"));
}
QTEST_MAIN(tst_qqmlengine)
#include "tst_qqmlengine.moc"

View File

@ -97,10 +97,12 @@ int main(int argc, char *argv[])
for (const QString &fn : qAsConst(args)) {
QV4::ScopedValue result(scope);
if (runAsModule) {
auto moduleUnit = vm.loadModule(QUrl::fromLocalFile(QFileInfo(fn).absoluteFilePath()));
if (moduleUnit) {
if (moduleUnit->instantiate(&vm))
moduleUnit->evaluate();
auto module = vm.loadModule(QUrl::fromLocalFile(QFileInfo(fn).absoluteFilePath()));
if (module.compiled) {
if (module.compiled->instantiate(&vm))
module.compiled->evaluate();
} else if (module.native) {
// Nothing to do. Native modules have no global code.
} else {
vm.throwError(QStringLiteral("Could not load module file"));
}