diff options
author | Alex Shaw <alex.shaw.as@gmail.com> | 2021-04-25 17:57:37 -0400 |
---|---|---|
committer | Alex Shaw <alex.shaw.as@gmail.com> | 2021-05-01 17:26:00 -0400 |
commit | 3464655f5e2adaa1aed8925a9a54b8fb5f238f31 (patch) | |
tree | ebb7c46c7d4f1af6e4b1853a74d7502fee3c450f /src/qml/jsruntime | |
parent | 689522817dc559056f7c55f811a189c4821b7787 (diff) |
Add QJSEngine::registerModule
Some applications that use JavaScript as a scripting language may want
to extend JS through C++ code. The current way to do that is with
global objects.
ES6 provides a better way of encapsulating code: modules.
registerModule() allows an application to provide a QJSValue as a named module.
Developers familiar with Node.js will find this very easy to use.
Example:
```c++
QJSValue num(666);
myEngine.registerModule("themarkofthebeast", num);
```
```js
import badnews from "themarkofthebeast";
```
[ChangeLog][QtQml][QJSEngine] Adds the ability to register QJSValues in
C++ as modules for importing in MJS files.
Change-Id: I0c98dcb746aa2aa15aa2ab3082129d106413a23b
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Diffstat (limited to 'src/qml/jsruntime')
-rw-r--r-- | src/qml/jsruntime/qv4engine.cpp | 17 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4engine_p.h | 8 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4executablecompilationunit.cpp | 72 |
3 files changed, 87 insertions, 10 deletions
diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index 69db5efd05..0c3f16651a 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -879,6 +879,10 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine) ExecutionEngine::~ExecutionEngine() { modules.clear(); + for (auto val : nativeModules) { + PersistentValueStorage::free(val); + } + nativeModules.clear(); qDeleteAll(m_extensionData); delete m_multiplyWrappedQObjects; m_multiplyWrappedQObjects = nullptr; @@ -2072,6 +2076,19 @@ QQmlRefPointer<ExecutableCompilationUnit> ExecutionEngine::loadModule(const QUrl return newModule; } +void ExecutionEngine::registerModule(const QString &_name, const QJSValue &module) +{ + const QUrl url(_name); + QMutexLocker moduleGuard(&moduleMutex); + const auto existingModule = nativeModules.find(url); + if (existingModule != nativeModules.end()) + return; + + QV4::Value* val = this->memoryManager->m_persistentValues->allocate(); + *val = QJSValuePrivate::asReturnedValue(&module); + nativeModules.insert(url, val); +} + bool ExecutionEngine::diskCacheEnabled() const { return (!disableDiskCache() && !debugger()) || forceDiskCache(); diff --git a/src/qml/jsruntime/qv4engine_p.h b/src/qml/jsruntime/qv4engine_p.h index e3e0a481cf..fe31b42c84 100644 --- a/src/qml/jsruntime/qv4engine_p.h +++ b/src/qml/jsruntime/qv4engine_p.h @@ -760,9 +760,17 @@ public: 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, QV4::Value*> nativeModules; + 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); bool diskCacheEnabled() const; diff --git a/src/qml/jsruntime/qv4executablecompilationunit.cpp b/src/qml/jsruntime/qv4executablecompilationunit.cpp index a4e7ce4466..f61bf36b0e 100644 --- a/src/qml/jsruntime/qv4executablecompilationunit.cpp +++ b/src/qml/jsruntime/qv4executablecompilationunit.cpp @@ -581,7 +581,11 @@ Heap::Module *ExecutableCompilationUnit::instantiate(ExecutionEngine *engine) setModule(module->d()); for (const QString &request: moduleRequests()) { - auto dependentModuleUnit = engine->loadModule(QUrl(request), this); + const QUrl url(request); + if (engine->nativeModules.contains(url)) + continue; + + auto dependentModuleUnit = engine->loadModule(url, this); if (engine->hasException) return nullptr; dependentModuleUnit->instantiate(engine); @@ -596,16 +600,62 @@ Heap::Module *ExecutableCompilationUnit::instantiate(ExecutionEngine *engine) } for (uint i = 0; i < importCount; ++i) { const CompiledData::ImportEntry &entry = data->importEntryTable()[i]; - auto dependentModuleUnit = engine->loadModule(urlAt(entry.moduleRequest), 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; + 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(); + + QV4::Value *value = nativeModule.value(); + if (value->isNullOrUndefined()) { + QString errorMessage = name; + errorMessage += QStringLiteral(" from "); + errorMessage += url.toString(); + errorMessage += QStringLiteral(" is null"); + engine->throwError(errorMessage); + return nullptr; + } + + if (name == QStringLiteral("default")) { + imports[i] = value; + } else { + url.setFragment(name); + auto fragment = engine->nativeModules.find(url); + if (fragment != engine->nativeModules.end()) { + imports[i] = fragment.value(); + } else { + Scope scope(this->engine); + ScopedObject o(scope, value); + if (!o) { + QString referenceErrorMessage = QStringLiteral("Unable to resolve import reference "); + referenceErrorMessage += name; + referenceErrorMessage += QStringLiteral(" because "); + referenceErrorMessage += url.toString(QUrl::RemoveFragment); + referenceErrorMessage += QStringLiteral(" is not an object"); + engine->throwReferenceError(referenceErrorMessage, fileName(), entry.location.line, entry.location.column); + return nullptr; + } + + 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); + } + } + } 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; } - imports[i] = valuePtr; } for (uint i = 0; i < data->indirectExportEntryTableSize; ++i) { @@ -745,6 +795,8 @@ void ExecutableCompilationUnit::evaluate() void ExecutableCompilationUnit::evaluateModuleRequests() { for (const QString &request: moduleRequests()) { + if (engine->nativeModules.contains(QUrl(request))) + continue; auto dependentModuleUnit = engine->loadModule(QUrl(request), this); if (engine->hasException) return; |