aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/jsruntime
diff options
context:
space:
mode:
authorAlex Shaw <alex.shaw.as@gmail.com>2021-04-25 17:57:37 -0400
committerAlex Shaw <alex.shaw.as@gmail.com>2021-05-01 17:26:00 -0400
commit3464655f5e2adaa1aed8925a9a54b8fb5f238f31 (patch)
treeebb7c46c7d4f1af6e4b1853a74d7502fee3c450f /src/qml/jsruntime
parent689522817dc559056f7c55f811a189c4821b7787 (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.cpp17
-rw-r--r--src/qml/jsruntime/qv4engine_p.h8
-rw-r--r--src/qml/jsruntime/qv4executablecompilationunit.cpp72
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;