diff options
| author | Ulf Hermann <ulf.hermann@qt.io> | 2025-06-02 16:15:47 +0200 |
|---|---|---|
| committer | Ulf Hermann <ulf.hermann@qt.io> | 2025-06-17 07:00:37 +0200 |
| commit | 2692b14cfb702ec4b50f05f743babba5973547e4 (patch) | |
| tree | 10aab44c9d4339b07e3da8f0f73140a1bcd79ef8 /src | |
| parent | d2bc4a4330254c0c68a0ade51b59a71c4b67b470 (diff) | |
QtQml: Allow remote JavaScript files in WorkerScript
Fixes: QTBUG-19407
Change-Id: I482689396db82332e50c41e6404d58376f4dc118
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'src')
| -rw-r--r-- | src/qml/qml/qqmlmetatype.cpp | 1 | ||||
| -rw-r--r-- | src/qml/qml/qqmlscriptblob.cpp | 6 | ||||
| -rw-r--r-- | src/qml/qml/qqmlscriptblob_p.h | 9 | ||||
| -rw-r--r-- | src/qml/qml/qqmlscriptdata_p.h | 1 | ||||
| -rw-r--r-- | src/qml/qml/qqmltypeloader.cpp | 106 | ||||
| -rw-r--r-- | src/qml/qml/qqmltypeloader_p.h | 20 | ||||
| -rw-r--r-- | src/qml/qml/qqmltypeloaderdata_p.h | 2 | ||||
| -rw-r--r-- | src/qmlworkerscript/qquickworkerscript.cpp | 116 | ||||
| -rw-r--r-- | src/qmlworkerscript/qquickworkerscript_p.h | 8 |
9 files changed, 170 insertions, 99 deletions
diff --git a/src/qml/qml/qqmlmetatype.cpp b/src/qml/qml/qqmlmetatype.cpp index 4a37c446e8..e397d20b9a 100644 --- a/src/qml/qml/qqmlmetatype.cpp +++ b/src/qml/qml/qqmlmetatype.cpp @@ -7,6 +7,7 @@ #include <private/qqmlmetatypedata_p.h> #include <private/qqmlpropertycachecreator_p.h> #include <private/qqmlscriptblob_p.h> +#include <private/qqmlscriptdata_p.h> #include <private/qqmltype_p_p.h> #include <private/qqmltypeloader_p.h> #include <private/qqmltypemodule_p.h> diff --git a/src/qml/qml/qqmlscriptblob.cpp b/src/qml/qml/qqmlscriptblob.cpp index d6b1fe80c0..16ef9aac88 100644 --- a/src/qml/qml/qqmlscriptblob.cpp +++ b/src/qml/qml/qqmlscriptblob.cpp @@ -4,7 +4,6 @@ #include <private/qqmlengine_p.h> #include <private/qqmlirbuilder_p.h> #include <private/qqmlscriptblob_p.h> -#include <private/qqmlscriptdata_p.h> #include <private/qqmlsourcecoordinate_p.h> #include <private/qqmlcontextdata_p.h> #include <private/qv4runtimecodegen_p.h> @@ -26,11 +25,6 @@ QQmlScriptBlob::~QQmlScriptBlob() { } -QQmlRefPointer<QQmlScriptData> QQmlScriptBlob::scriptData() const -{ - return m_scriptData; -} - void QQmlScriptBlob::dataReceived(const SourceCodeData &data) { assertTypeLoaderThread(); diff --git a/src/qml/qml/qqmlscriptblob_p.h b/src/qml/qml/qqmlscriptblob_p.h index 4d882b7f4f..583bb6d07a 100644 --- a/src/qml/qml/qqmlscriptblob_p.h +++ b/src/qml/qml/qqmlscriptblob_p.h @@ -15,13 +15,13 @@ // We mean it. // -#include <private/qqmltypeloader_p.h> #include <private/qqmlnotifyingblob_p.h> +#include <private/qqmlscriptdata_p.h> +#include <private/qqmltypeloader_p.h> QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(DBG_DISK_CACHE) -class QQmlScriptData; class Q_AUTOTEST_EXPORT QQmlScriptBlob : public QQmlNotifyingBlob { private: @@ -40,7 +40,10 @@ public: QQmlRefPointer<QQmlScriptBlob> script; }; - QQmlRefPointer<QQmlScriptData> scriptData() const; + QQmlRefPointer<QQmlScriptData> scriptData() const + { + return m_scriptData; + } protected: void dataReceived(const SourceCodeData &) override; diff --git a/src/qml/qml/qqmlscriptdata_p.h b/src/qml/qml/qqmlscriptdata_p.h index 6eb3171505..f54b8b75a1 100644 --- a/src/qml/qml/qqmlscriptdata_p.h +++ b/src/qml/qml/qqmlscriptdata_p.h @@ -16,7 +16,6 @@ // #include <private/qqmlrefcount_p.h> -#include <private/qqmlscriptblob_p.h> #include <private/qv4value_p.h> #include <private/qv4persistent_p.h> #include <private/qv4compileddata_p.h> diff --git a/src/qml/qml/qqmltypeloader.cpp b/src/qml/qml/qqmltypeloader.cpp index d4d711332b..0d89f93a3f 100644 --- a/src/qml/qml/qqmltypeloader.cpp +++ b/src/qml/qml/qqmltypeloader.cpp @@ -1295,6 +1295,32 @@ QQmlTypeLoader::~QQmlTypeLoader() clearQmldirInfo(); } +template<typename Blob> +QQmlRefPointer<Blob> handleExisting( + const QQmlTypeLoaderSharedDataPtr &data, QQmlRefPointer<Blob> &&blob, + QQmlTypeLoader::Mode mode) +{ + if ((mode == QQmlTypeLoader::PreferSynchronous && QQmlFile::isSynchronous(blob->finalUrl())) + || mode == QQmlTypeLoader::Synchronous) { + // this was started Asynchronous, but we need to force Synchronous + // completion now. + + // This only works when called directly from e.g. the UI thread, but not + // when recursively called on the QML thread via resolveTypes() + + // NB: We do not want to know whether the thread is the main thread, but specifically + // that the thread is _not_ the thread we're waiting for. + // If !QT_CONFIG(qml_type_loader_thread) the QML thread is the main thread. + + QQmlTypeLoaderThread *thread = data.thread(); + if (thread && !thread->isThisThread()) { + while (!blob->isCompleteOrError()) + thread->waitForNextMessage(); // Requires lock to be held, via data above + } + } + return blob; +} + /*! Returns a QQmlTypeData for the specified \a url. The QQmlTypeData may be cached. */ @@ -1306,34 +1332,14 @@ QQmlRefPointer<QQmlTypeData> QQmlTypeLoader::getType(const QUrl &unNormalizedUrl (QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl).isEmpty() || !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl)))); - const QUrl url = QQmlMetaType::normalizedUrl(unNormalizedUrl); - - const auto handleExisting = [&](const QQmlRefPointer<QQmlTypeData> &typeData) { - if ((mode == PreferSynchronous || mode == Synchronous) && QQmlFile::isSynchronous(url)) { - // this was started Asynchronous, but we need to force Synchronous - // completion now (if at all possible with this type of URL). - - // This only works when called directly from e.g. the UI thread, but not - // when recursively called on the QML thread via resolveTypes() - - // NB: We do not want to know whether the thread is the main thread, but specifically - // that the thread is _not_ the thread we're waiting for. - // If !QT_CONFIG(qml_type_loader_thread) the QML thread is the main thread. - if (thread() && !thread()->isThisThread()) { - while (!typeData->isCompleteOrError()) - thread()->waitForNextMessage(); // Requires lock to be held, via data above - } - } - return typeData; - }; - QQmlRefPointer<QQmlTypeData> typeData; { + const QUrl url = QQmlMetaType::normalizedUrl(unNormalizedUrl); QQmlTypeLoaderSharedDataPtr data(&m_data); typeData = data->typeCache.value(url); if (typeData) - return handleExisting(typeData); + return handleExisting(data, std::move(typeData), mode); // Trim before adding the new type, so that we don't immediately trim it away if (data->typeCache.size() >= data->typeCacheTrimThreshold) @@ -1345,18 +1351,7 @@ QQmlRefPointer<QQmlTypeData> QQmlTypeLoader::getType(const QUrl &unNormalizedUrl data->typeCache.insert(url, typeData); } - QQmlMetaType::CachedUnitLookupError error = QQmlMetaType::CachedUnitLookupError::NoError; - const QQmlMetaType::CacheMode cacheMode = aotCacheMode(); - if (const QQmlPrivate::CachedQmlUnit *cachedUnit = (cacheMode != QQmlMetaType::RejectAll) - ? QQmlMetaType::findCachedCompilationUnit(typeData->url(), cacheMode, &error) - : nullptr) { - loadWithCachedUnit(QQmlDataBlob::Ptr(typeData.data()), cachedUnit, mode); - } else { - typeData->setCachedUnitStatus(error); - load(QQmlDataBlob::Ptr(typeData.data()), mode); - } - - return typeData; + return finalizeBlob(std::move(typeData), mode); } /*! @@ -1378,6 +1373,32 @@ static bool isModuleUrl(const QUrl &url) return url.path().endsWith(QLatin1String(".mjs")); } +QQmlRefPointer<QQmlScriptBlob> QQmlTypeLoader::getScript(const QUrl &unNormalizedUrl, Mode mode) +{ + // This can be called from either thread. + + Q_ASSERT(!unNormalizedUrl.isRelative() && + (QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl).isEmpty() || + !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl)))); + + QQmlRefPointer<QQmlScriptBlob> scriptBlob; + { + const QUrl url = QQmlMetaType::normalizedUrl(unNormalizedUrl); + QQmlTypeLoaderSharedDataPtr data(&m_data); + + scriptBlob = data->scriptCache.value(url); + if (scriptBlob) + return handleExisting(data, std::move(scriptBlob), mode); + + scriptBlob = QQml::makeRefPointer<QQmlScriptBlob>(url, this, isModuleUrl(url) + ? QQmlScriptBlob::IsESModule::Yes + : QQmlScriptBlob::IsESModule::No); + data->scriptCache.insert(url, scriptBlob); + } + + return finalizeBlob(std::move(scriptBlob), mode); +} + QQmlRefPointer<QV4::CompiledData::CompilationUnit> QQmlTypeLoader::injectModule( const QUrl &relativeUrl, const QV4::CompiledData::Unit *unit) { @@ -1398,7 +1419,8 @@ QQmlRefPointer<QV4::CompiledData::CompilationUnit> QQmlTypeLoader::injectModule( } /*! -Return a QQmlScriptBlob for \a url. The QQmlScriptData may be cached. +Return a QQmlScriptBlob for \a unNormalizedUrl or \a relativeUrl. +This assumes PreferSynchronous, and therefore the result may not be ready yet. */ QQmlRefPointer<QQmlScriptBlob> QQmlTypeLoader::getScript( const QUrl &unNormalizedUrl, const QUrl &relativeUrl) @@ -1421,6 +1443,7 @@ QQmlRefPointer<QQmlScriptBlob> QQmlTypeLoader::getScript( if (!scriptBlob && unNormalizedUrl != relativeUrl) scriptBlob = data->scriptCache.value(relativeUrl); + // Do not try to finish the loading via handleExisting() here. if (scriptBlob) return scriptBlob; @@ -1430,18 +1453,7 @@ QQmlRefPointer<QQmlScriptBlob> QQmlTypeLoader::getScript( data->scriptCache.insert(url, scriptBlob); } - QQmlMetaType::CachedUnitLookupError error = QQmlMetaType::CachedUnitLookupError::NoError; - const QQmlMetaType::CacheMode cacheMode = aotCacheMode(); - if (const QQmlPrivate::CachedQmlUnit *cachedUnit = (cacheMode != QQmlMetaType::RejectAll) - ? QQmlMetaType::findCachedCompilationUnit(scriptBlob->url(), cacheMode, &error) - : nullptr) { - QQmlTypeLoader::loadWithCachedUnit(QQmlDataBlob::Ptr(scriptBlob.data()), cachedUnit); - } else { - scriptBlob->setCachedUnitStatus(error); - QQmlTypeLoader::load(QQmlDataBlob::Ptr(scriptBlob.data())); - } - - return scriptBlob; + return finalizeBlob(std::move(scriptBlob), PreferSynchronous); } /*! diff --git a/src/qml/qml/qqmltypeloader_p.h b/src/qml/qml/qqmltypeloader_p.h index d207acdf34..82b978a292 100644 --- a/src/qml/qml/qqmltypeloader_p.h +++ b/src/qml/qml/qqmltypeloader_p.h @@ -183,6 +183,9 @@ public: QQmlRefPointer<QQmlTypeData> getType( const QByteArray &data, const QUrl &url, Mode mode = PreferSynchronous); + QQmlRefPointer<QQmlScriptBlob> getScript( + const QUrl &unNormalizedUrl, Mode mode = PreferSynchronous); + QQmlRefPointer<QV4::CompiledData::CompilationUnit> injectModule( const QUrl &relativeUrl, const QV4::CompiledData::Unit *unit); @@ -360,6 +363,23 @@ private: return true; } + template<typename Blob> + QQmlRefPointer<Blob> finalizeBlob(QQmlRefPointer<Blob> &&blob, QQmlTypeLoader::Mode mode) + { + QQmlMetaType::CachedUnitLookupError error = QQmlMetaType::CachedUnitLookupError::NoError; + const QQmlMetaType::CacheMode cacheMode = aotCacheMode(); + if (const QQmlPrivate::CachedQmlUnit *cachedUnit = (cacheMode != QQmlMetaType::RejectAll) + ? QQmlMetaType::findCachedCompilationUnit(blob->url(), cacheMode, &error) + : nullptr) { + loadWithCachedUnit(QQmlDataBlob::Ptr(blob.data()), cachedUnit, mode); + } else { + blob->setCachedUnitStatus(error); + load(QQmlDataBlob::Ptr(blob.data()), mode); + } + + return blob; + } + QQmlMetaType::CacheMode aotCacheMode(); QQmlTypeLoaderLockedData m_data; diff --git a/src/qml/qml/qqmltypeloaderdata_p.h b/src/qml/qml/qqmltypeloaderdata_p.h index dc2cf7f549..f509b1e625 100644 --- a/src/qml/qml/qqmltypeloaderdata_p.h +++ b/src/qml/qml/qqmltypeloaderdata_p.h @@ -222,6 +222,8 @@ public: Data *operator->() const { return &data->m_sharedData; } operator Data *() const { return &data->m_sharedData; } + QQmlTypeLoaderThread *thread() const { return data->thread(); } + private: LockedData *data = nullptr; }; diff --git a/src/qmlworkerscript/qquickworkerscript.cpp b/src/qmlworkerscript/qquickworkerscript.cpp index 14cd783c32..732c272582 100644 --- a/src/qmlworkerscript/qquickworkerscript.cpp +++ b/src/qmlworkerscript/qquickworkerscript.cpp @@ -3,9 +3,12 @@ #include "qtqmlworkerscriptglobal_p.h" #include "qquickworkerscript_p.h" + #include <private/qqmlengine_p.h> #include <private/qqmlexpression_p.h> #include <private/qjsvalue_p.h> +#include <private/qqmlscriptblob_p.h> +#include <private/qqmlscriptdata_p.h> #include <QtCore/qcoreevent.h> #include <QtCore/qcoreapplication.h> @@ -38,6 +41,7 @@ enum class WorkerEventType Load, Remove, Error, + Ready, Destroy = QEvent::User + 100, }; @@ -99,6 +103,12 @@ private: QQmlError m_error; }; +class WorkerReadyEvent : public QEvent +{ +public: + WorkerReadyEvent() : QEvent(QEvent::Type(WorkerEventType::Ready)) {} +}; + class WorkerDestroyEvent : public QEvent { public: @@ -107,18 +117,23 @@ public: struct WorkerScript : public QV4::ExecutionEngine::Deletable + , public QQmlNotifyingBlob::Callback #if QT_CONFIG(qml_network) , public QQmlNetworkAccessManagerFactory #endif { - WorkerScript(QV4::ExecutionEngine *); + WorkerScript(QV4::ExecutionEngine *engine); ~WorkerScript() = default; + QV4::ExecutionEngine *engine = nullptr; QQuickWorkerScriptEnginePrivate *p = nullptr; QQuickWorkerScript *owner = nullptr; + #if QT_CONFIG(qml_network) QNetworkAccessManager *create(QObject *parent) final; #endif + + void ready(QQmlNotifyingBlob *blob) final; }; V4_DEFINE_EXTENSION(WorkerScript, workerScriptExtension); @@ -148,6 +163,9 @@ public: static QV4::ReturnedValue method_sendMessage(const QV4::FunctionObject *, const QV4::Value *thisObject, const QV4::Value *argv, int argc); QV4::ExecutionEngine *workerEngine(int id); + void reportScriptReady(WorkerScript *); + void reportScriptException(WorkerScript *, const QQmlError &error); + signals: void stopThread(); @@ -155,9 +173,8 @@ protected: bool event(QEvent *) override; private: - void processMessage(int, const QByteArray &); - void processLoad(int, const QUrl &); - void reportScriptException(WorkerScript *, const QQmlError &error); + void processMessage(int id, const QByteArray &data); + void processLoad(int id, const QUrl &url); }; QV4::ReturnedValue QQuickWorkerScriptEnginePrivate::method_sendMessage(const QV4::FunctionObject *b, @@ -226,10 +243,6 @@ QV4::ExecutionEngine *QQuickWorkerScriptEnginePrivate::workerEngine(int id) WorkerScript *script = workerScriptExtension(engine); script->owner = owner; script->p = this; -#if QT_CONFIG(qml_network) - // Eagerly create a network access manager that can outlive the parent engine. - engine->getNetworkAccessManager(); -#endif *it = engine; return engine; } @@ -268,39 +281,24 @@ void QQuickWorkerScriptEnginePrivate::processLoad(int id, const QUrl &url) if (url.isRelative()) return; - QString fileName = QQmlFile::urlToLocalFileOrQrc(url); - QV4::ExecutionEngine *engine = workerEngine(id); if (!engine) return; WorkerScript *script = workerScriptExtension(engine); + QQmlRefPointer<QQmlScriptBlob> scriptBlob = engine->typeLoader()->getScript(url); - if (fileName.endsWith(QLatin1String(".mjs"))) { - if (auto module = engine->loadModule(url)) { - if (module->instantiate()) - module->evaluate(); - } else { - engine->throwError(QStringLiteral("Could not load module file")); - } - } else { - QString error; - QV4::Scope scope(engine); - QScopedPointer<QV4::Script> program; - program.reset(QV4::Script::createFromFileOrCache( - engine, /*qmlContext*/nullptr, fileName, url, &error)); - if (program.isNull()) { - if (!error.isEmpty()) - qWarning().nospace() << error; - return; - } - - if (!engine->hasException) - program->run(); - } + if (scriptBlob->isCompleteOrError()) + script->ready(scriptBlob.data()); + else + scriptBlob->registerCallback(script); +} - if (engine->hasException) - reportScriptException(script, engine->catchExceptionAsQmlError()); +void QQuickWorkerScriptEnginePrivate::reportScriptReady(WorkerScript *script) +{ + QMutexLocker locker(&script->p->m_lock); + if (script->owner) + QCoreApplication::postEvent(script->owner, new WorkerReadyEvent); } void QQuickWorkerScriptEnginePrivate::reportScriptException(WorkerScript *script, @@ -340,7 +338,7 @@ QQuickWorkerScriptEngine::~QQuickWorkerScriptEngine() } -WorkerScript::WorkerScript(QV4::ExecutionEngine *engine) +WorkerScript::WorkerScript(QV4::ExecutionEngine *engine) : engine(engine) { engine->initQmlGlobalObject(); @@ -360,6 +358,38 @@ WorkerScript::WorkerScript(QV4::ExecutionEngine *engine) #endif // qml_network } +void WorkerScript::ready(QQmlNotifyingBlob *scriptBlob) +{ + if (scriptBlob->isComplete()) { + const auto cu = engine->executableCompilationUnit( + static_cast<QQmlScriptBlob *>(scriptBlob)->scriptData()->compilationUnit()); + if (cu->isESModule()) { + if (cu->instantiate()) + cu->evaluate(); + } else { + QV4::Function *vmFunction = cu->rootFunction(); + QScopedValueRollback<QV4::Function *> savedGlobal(engine->globalCode, vmFunction); + vmFunction->call(engine->globalObject, nullptr, 0, engine->rootContext()); + } + + if (engine->hasException) + p->reportScriptException(this, engine->catchExceptionAsQmlError()); + + } else { + Q_ASSERT(scriptBlob->isError()); + + const QList<QQmlError> errors = scriptBlob->errors(); + for (const QQmlError &error : errors) { + // Funnel this through SyntaxError to get the right output format. + engine->throwSyntaxError( + error.description(), error.url().toString(), error.line(), error.column()); + p->reportScriptException(this, engine->catchExceptionAsQmlError()); + } + } + + p->reportScriptReady(this); +} + #if QT_CONFIG(qml_network) QNetworkAccessManager *WorkerScript::create(QObject *parent) { @@ -478,7 +508,7 @@ void QQuickWorkerScriptEngine::run() Scripts that are ECMAScript modules can freely use import and export statements. */ QQuickWorkerScript::QQuickWorkerScript(QObject *parent) -: QObject(parent), m_engine(nullptr), m_scriptId(-1), m_componentComplete(true) +: QObject(parent) { } @@ -512,6 +542,11 @@ void QQuickWorkerScript::setSource(const QUrl &source) if (engine()) { const QQmlContext *context = qmlContext(this); m_engine->executeUrl(m_scriptId, context ? context->resolvedUrl(m_source) : m_source); + if (m_ready) { + // While the new script is loading, we can't accept any events. + m_ready = false; + emit readyChanged(); + } } emit sourceChanged(); @@ -525,7 +560,7 @@ void QQuickWorkerScript::setSource(const QUrl &source) */ bool QQuickWorkerScript::ready() const { - return m_engine != nullptr; + return m_ready; } /*! @@ -589,8 +624,6 @@ QQuickWorkerScriptEngine *QQuickWorkerScript::engine() if (m_source.isValid()) m_engine->executeUrl(m_scriptId, context->resolvedUrl(m_source)); - emit readyChanged(); - return m_engine; } return nullptr; @@ -625,6 +658,11 @@ bool QQuickWorkerScript::event(QEvent *event) QQmlEnginePrivate::warning(qmlEngine(this), workerEvent->error()); return true; } + case WorkerEventType::Ready: + Q_ASSERT(!m_ready); + m_ready = true; + emit readyChanged(); + return true; default: break; } diff --git a/src/qmlworkerscript/qquickworkerscript_p.h b/src/qmlworkerscript/qquickworkerscript_p.h index 1c4c0500fe..ad2b958c6d 100644 --- a/src/qmlworkerscript/qquickworkerscript_p.h +++ b/src/qmlworkerscript/qquickworkerscript_p.h @@ -82,10 +82,12 @@ protected: private: QQuickWorkerScriptEngine *engine(); - QQuickWorkerScriptEngine *m_engine; - int m_scriptId; + QUrl m_source; - bool m_componentComplete; + QQuickWorkerScriptEngine *m_engine = nullptr; + int m_scriptId = -1; + bool m_componentComplete = true; + bool m_ready = false; }; QT_END_NAMESPACE |
