diff options
Diffstat (limited to 'src/qml/qml/qqmltypeloader.cpp')
-rw-r--r-- | src/qml/qml/qqmltypeloader.cpp | 774 |
1 files changed, 492 insertions, 282 deletions
diff --git a/src/qml/qml/qqmltypeloader.cpp b/src/qml/qml/qqmltypeloader.cpp index 54f94d6a11..a74397bd93 100644 --- a/src/qml/qml/qqmltypeloader.cpp +++ b/src/qml/qml/qqmltypeloader.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include <private/qqmltypeloader_p.h> @@ -45,12 +9,15 @@ #include <private/qqmltypedata_p.h> #include <private/qqmltypeloaderqmldircontent_p.h> #include <private/qqmltypeloaderthread_p.h> +#include <private/qqmlsourcecoordinate_p.h> #include <QtQml/qqmlabstracturlinterceptor.h> #include <QtQml/qqmlengine.h> #include <QtQml/qqmlextensioninterface.h> #include <QtQml/qqmlfile.h> +#include <qtqml_tracepoints_p.h> + #include <QtCore/qdir.h> #include <QtCore/qdiriterator.h> #include <QtCore/qfile.h> @@ -65,8 +32,6 @@ #define ASSERT_LOADTHREAD() #endif -DEFINE_BOOL_CONFIG_OPTION(disableDiskCache, QML_DISABLE_DISK_CACHE); -DEFINE_BOOL_CONFIG_OPTION(forceDiskCache, QML_FORCE_DISK_CACHE); QT_BEGIN_NAMESPACE @@ -81,6 +46,9 @@ namespace { }; } +Q_TRACE_POINT(qtqml, QQmlCompiling_entry, const QUrl &url) +Q_TRACE_POINT(qtqml, QQmlCompiling_exit) + /*! \class QQmlTypeLoader \brief The QQmlTypeLoader class abstracts loading files and their dependencies over the network. @@ -123,8 +91,6 @@ void QQmlTypeLoader::invalidate() // Need to delete the network replies after // the loader thread is shutdown as it could be // getting new replies while we clear them - for (NetworkReplies::Iterator iter = m_networkReplies.begin(); iter != m_networkReplies.end(); ++iter) - (*iter)->release(); m_networkReplies.clear(); #endif // qml_network } @@ -171,8 +137,8 @@ struct StaticLoader { }; struct CachedLoader { - const QV4::CompiledData::Unit *unit; - CachedLoader(const QV4::CompiledData::Unit *unit) : unit(unit) {} + const QQmlPrivate::CachedQmlUnit *unit; + CachedLoader(const QQmlPrivate::CachedQmlUnit *unit) : unit(unit) {} void loadThread(QQmlTypeLoader *loader, QQmlDataBlob *blob) const { @@ -216,9 +182,7 @@ void QQmlTypeLoader::doLoad(const Loader &loader, QQmlDataBlob *blob, Mode mode) } else { Q_ASSERT(mode == Synchronous); while (!blob->isCompleteOrError()) { - unlock(); m_thread->waitForNextMessage(); - lock(); } } } @@ -244,26 +208,26 @@ void QQmlTypeLoader::loadWithStaticData(QQmlDataBlob *blob, const QByteArray &da doLoad(StaticLoader(data), blob, mode); } -void QQmlTypeLoader::loadWithCachedUnit(QQmlDataBlob *blob, const QV4::CompiledData::Unit *unit, Mode mode) +void QQmlTypeLoader::loadWithCachedUnit(QQmlDataBlob *blob, const QQmlPrivate::CachedQmlUnit *unit, Mode mode) { doLoad(CachedLoader(unit), blob, mode); } -void QQmlTypeLoader::loadWithStaticDataThread(QQmlDataBlob *blob, const QByteArray &data) +void QQmlTypeLoader::loadWithStaticDataThread(const QQmlDataBlob::Ptr &blob, const QByteArray &data) { ASSERT_LOADTHREAD(); setData(blob, data); } -void QQmlTypeLoader::loadWithCachedUnitThread(QQmlDataBlob *blob, const QV4::CompiledData::Unit *unit) +void QQmlTypeLoader::loadWithCachedUnitThread(const QQmlDataBlob::Ptr &blob, const QQmlPrivate::CachedQmlUnit *unit) { ASSERT_LOADTHREAD(); setCachedUnit(blob, unit); } -void QQmlTypeLoader::loadThread(QQmlDataBlob *blob) +void QQmlTypeLoader::loadThread(const QQmlDataBlob::Ptr &blob) { ASSERT_LOADTHREAD(); @@ -289,7 +253,7 @@ void QQmlTypeLoader::loadThread(QQmlDataBlob *blob) return; } - blob->m_data.setProgress(0xFF); + blob->m_data.setProgress(1.f); if (blob->m_data.isAsync()) m_thread->callDownloadProgressChanged(blob, 1.); @@ -299,7 +263,6 @@ void QQmlTypeLoader::loadThread(QQmlDataBlob *blob) #if QT_CONFIG(qml_network) QNetworkReply *reply = m_thread->networkAccessManager()->get(QNetworkRequest(blob->m_url)); QQmlTypeLoaderNetworkReplyProxy *nrp = m_thread->networkReplyProxy(); - blob->addref(); m_networkReplies.insert(reply, blob); if (reply->isFinished()) { @@ -331,7 +294,7 @@ void QQmlTypeLoader::networkReplyFinished(QNetworkReply *reply) reply->deleteLater(); - QQmlDataBlob *blob = m_networkReplies.take(reply); + QQmlRefPointer<QQmlDataBlob> blob = m_networkReplies.take(reply); Q_ASSERT(blob); @@ -347,7 +310,7 @@ void QQmlTypeLoader::networkReplyFinished(QNetworkReply *reply) QNetworkReply *reply = m_thread->networkAccessManager()->get(QNetworkRequest(url)); QObject *nrp = m_thread->networkReplyProxy(); QObject::connect(reply, SIGNAL(finished()), nrp, SLOT(finished())); - m_networkReplies.insert(reply, blob); + m_networkReplies.insert(reply, std::move(blob)); #ifdef DATABLOB_DEBUG qWarning("QQmlDataBlob: redirected to %s", qPrintable(blob->finalUrlString())); #endif @@ -361,8 +324,6 @@ void QQmlTypeLoader::networkReplyFinished(QNetworkReply *reply) QByteArray data = reply->readAll(); setData(blob, data); } - - blob->release(); } void QQmlTypeLoader::networkReplyProgress(QNetworkReply *reply, @@ -370,12 +331,12 @@ void QQmlTypeLoader::networkReplyProgress(QNetworkReply *reply, { Q_ASSERT(m_thread->isThisThread()); - QQmlDataBlob *blob = m_networkReplies.value(reply); + const QQmlRefPointer<QQmlDataBlob> blob = m_networkReplies.value(reply); Q_ASSERT(blob); if (bytesTotal != 0) { - quint8 progress = 0xFF * (qreal(bytesReceived) / qreal(bytesTotal)); + qreal progress = (qreal(bytesReceived) / qreal(bytesTotal)); blob->m_data.setProgress(progress); if (blob->m_data.isAsync()) m_thread->callDownloadProgressChanged(blob, blob->m_data.progress()); @@ -391,7 +352,7 @@ QQmlEngine *QQmlTypeLoader::engine() const return m_engine; } -/*! +/*! \internal Call the initializeEngine() method on \a iface. Used by QQmlImportDatabase to ensure it gets called in the correct thread. */ @@ -419,7 +380,7 @@ void QQmlTypeLoader::initializeEngine(QQmlExtensionInterface *iface, const char doInitializeEngine(iface, m_thread, engine(), uri); } -void QQmlTypeLoader::setData(QQmlDataBlob *blob, const QByteArray &data) +void QQmlTypeLoader::setData(const QQmlDataBlob::Ptr &blob, const QByteArray &data) { QQmlDataBlob::SourceCodeData d; d.inlineSourceCode = QString::fromUtf8(data); @@ -427,16 +388,17 @@ void QQmlTypeLoader::setData(QQmlDataBlob *blob, const QByteArray &data) setData(blob, d); } -void QQmlTypeLoader::setData(QQmlDataBlob *blob, const QString &fileName) +void QQmlTypeLoader::setData(const QQmlDataBlob::Ptr &blob, const QString &fileName) { QQmlDataBlob::SourceCodeData d; d.fileInfo = QFileInfo(fileName); setData(blob, d); } -void QQmlTypeLoader::setData(QQmlDataBlob *blob, const QQmlDataBlob::SourceCodeData &d) +void QQmlTypeLoader::setData(const QQmlDataBlob::Ptr &blob, const QQmlDataBlob::SourceCodeData &d) { - QQmlCompilingProfiler prof(profiler(), blob); + Q_TRACE_SCOPE(QQmlCompiling, blob->url()); + QQmlCompilingProfiler prof(profiler(), blob.data()); blob->m_inCallback = true; @@ -453,9 +415,10 @@ void QQmlTypeLoader::setData(QQmlDataBlob *blob, const QQmlDataBlob::SourceCodeD blob->tryDone(); } -void QQmlTypeLoader::setCachedUnit(QQmlDataBlob *blob, const QV4::CompiledData::Unit *unit) +void QQmlTypeLoader::setCachedUnit(const QQmlDataBlob::Ptr &blob, const QQmlPrivate::CachedQmlUnit *unit) { - QQmlCompilingProfiler prof(profiler(), blob); + Q_TRACE_SCOPE(QQmlCompiling, blob->url()); + QQmlCompilingProfiler prof(profiler(), blob.data()); blob->m_inCallback = true; @@ -478,18 +441,21 @@ void QQmlTypeLoader::shutdownThread() m_thread->shutdown(); } -QQmlTypeLoader::Blob::PendingImport::PendingImport(QQmlTypeLoader::Blob *blob, const QV4::CompiledData::Import *import) +QQmlTypeLoader::Blob::PendingImport::PendingImport( + QQmlTypeLoader::Blob *blob, const QV4::CompiledData::Import *import, + QQmlImports::ImportFlags flags) + : uri(blob->stringAt(import->uriIndex)) + , qualifier(blob->stringAt(import->qualifierIndex)) + , type(static_cast<QV4::CompiledData::Import::ImportType>(quint32(import->type))) + , location(import->location) + , flags(flags) + , version(import->version) { - type = static_cast<QV4::CompiledData::Import::ImportType>(quint32(import->type)); - uri = blob->stringAt(import->uriIndex); - qualifier = blob->stringAt(import->qualifierIndex); - majorVersion = import->majorVersion; - minorVersion = import->minorVersion; - location = import->location; } QQmlTypeLoader::Blob::Blob(const QUrl &url, QQmlDataBlob::Type type, QQmlTypeLoader *loader) - : QQmlDataBlob(url, type, loader), m_importCache(loader) + : QQmlDataBlob(url, type, loader) + , m_importCache(new QQmlImports(), QQmlRefPointer<QQmlImports>::Adopt) { } @@ -501,8 +467,7 @@ bool QQmlTypeLoader::Blob::fetchQmldir(const QUrl &url, PendingImportPtr import, { QQmlRefPointer<QQmlQmldirData> data = typeLoader()->getQmldir(url); - data->setImport(this, std::move(import)); - data->setPriority(this, priority); + data->setPriority(this, std::move(import), priority); if (data->status() == Error) { // This qmldir must not exist - which is not an error @@ -517,188 +482,387 @@ bool QQmlTypeLoader::Blob::fetchQmldir(const QUrl &url, PendingImportPtr import, return true; } -bool QQmlTypeLoader::Blob::updateQmldir(const QQmlRefPointer<QQmlQmldirData> &data, PendingImportPtr import, QList<QQmlError> *errors) +/*! + * \internal + * Import any qualified scripts of for \a import as listed in \a qmldir. + * Precondition is that \a import is actually qualified. + */ +void QQmlTypeLoader::Blob::importQmldirScripts( + const QQmlTypeLoader::Blob::PendingImportPtr &import, + const QQmlTypeLoaderQmldirContent &qmldir, const QUrl &qmldirUrl) +{ + const auto qmldirScripts = qmldir.scripts(); + for (const QQmlDirParser::Script &script : qmldirScripts) { + const QUrl scriptUrl = qmldirUrl.resolved(QUrl(script.fileName)); + QQmlRefPointer<QQmlScriptBlob> blob = typeLoader()->getScript(scriptUrl); + addDependency(blob.data()); + scriptImported(blob, import->location, script.nameSpace, import->qualifier); + } +} + +template<typename URL> +void postProcessQmldir( + QQmlTypeLoader::Blob *self, + const QQmlTypeLoader::Blob::PendingImportPtr &import, const QString &qmldirFilePath, + const URL &qmldirUrl) +{ + const QQmlTypeLoaderQmldirContent qmldir = self->typeLoader()->qmldirContent(qmldirFilePath); + if (!import->qualifier.isEmpty()) + self->importQmldirScripts(import, qmldir, QUrl(qmldirUrl)); + + if (qmldir.plugins().isEmpty()) { + // If the qmldir does not register a plugin, we might still have declaratively + // registered types (if we are dealing with an application instead of a library) + // We should use module name given in the qmldir rather than the one given by the + // import since the import may be a directory import. + auto module = QQmlMetaType::typeModule(qmldir.typeNamespace(), import->version); + if (!module) + QQmlMetaType::qmlRegisterModuleTypes(qmldir.typeNamespace()); + // else: If the module already exists, the types must have been already registered + } +} + +bool QQmlTypeLoader::Blob::updateQmldir(const QQmlRefPointer<QQmlQmldirData> &data, const QQmlTypeLoader::Blob::PendingImportPtr &import, QList<QQmlError> *errors) { QString qmldirIdentifier = data->urlString(); QString qmldirUrl = qmldirIdentifier.left(qmldirIdentifier.lastIndexOf(QLatin1Char('/')) + 1); typeLoader()->setQmldirContent(qmldirIdentifier, data->content()); - if (!m_importCache.updateQmldirContent(typeLoader()->importDatabase(), import->uri, import->qualifier, qmldirIdentifier, qmldirUrl, errors)) + const QTypeRevision version = m_importCache->updateQmldirContent( + typeLoader(), import->uri, import->qualifier, qmldirIdentifier, qmldirUrl, errors); + if (!version.isValid()) return false; - if (!loadImportDependencies(import, qmldirIdentifier, errors)) + // Use more specific version for dependencies if possible + if (version.hasMajorVersion()) + import->version = version; + + if (!loadImportDependencies(import, qmldirIdentifier, import->flags, errors)) return false; - import->priority = data->priority(this); + import->priority = 0; // Release this reference at destruction m_qmldirs << data; - if (!import->qualifier.isEmpty()) { - // Does this library contain any qualified scripts? - QUrl libraryUrl(qmldirUrl); - const QQmlTypeLoaderQmldirContent qmldir = typeLoader()->qmldirContent(qmldirIdentifier); - const auto qmldirScripts = qmldir.scripts(); - for (const QQmlDirParser::Script &script : qmldirScripts) { - QUrl scriptUrl = libraryUrl.resolved(QUrl(script.fileName)); - QQmlRefPointer<QQmlScriptBlob> blob = typeLoader()->getScript(scriptUrl); - addDependency(blob.data()); - - scriptImported(blob, import->location, script.nameSpace, import->qualifier); - } - } + postProcessQmldir(this, import, qmldirIdentifier, qmldirUrl); + return true; +} +bool QQmlTypeLoader::Blob::addScriptImport(const QQmlTypeLoader::Blob::PendingImportPtr &import) +{ + const QUrl url(import->uri); + QQmlTypeLoader *loader = typeLoader(); + QQmlRefPointer<QQmlScriptBlob> blob = loader->injectedScript(url); + if (!blob) + blob = loader->getScript(finalUrl().resolved(url)); + else + Q_ASSERT(blob->status() == QQmlDataBlob::Status::Complete); + addDependency(blob.data()); + scriptImported(blob, import->location, import->qualifier, QString()); return true; } -bool QQmlTypeLoader::Blob::addImport(const QV4::CompiledData::Import *import, QList<QQmlError> *errors) +bool QQmlTypeLoader::Blob::addFileImport(const QQmlTypeLoader::Blob::PendingImportPtr &import, QList<QQmlError> *errors) { - return addImport(std::make_shared<PendingImport>(this, import), errors); + QQmlImports::ImportFlags flags; + + QUrl importUrl(import->uri); + QString path = importUrl.path(); + path.append(QLatin1String(path.endsWith(QLatin1Char('/')) ? "qmldir" : "/qmldir")); + importUrl.setPath(path); + QUrl qmldirUrl = finalUrl().resolved(importUrl); + if (!QQmlImports::isLocal(qmldirUrl)) { + // This is a remote file; the import is currently incomplete + flags = QQmlImports::ImportIncomplete; + } + + const QTypeRevision version = m_importCache->addFileImport( + typeLoader(), import->uri, import->qualifier, import->version, flags, + import->precedence, nullptr, errors); + if (!version.isValid()) + return false; + + // Use more specific version for the qmldir if possible + if (version.hasMajorVersion()) + import->version = version; + + if (flags & QQmlImports::ImportIncomplete) { + if (!fetchQmldir(qmldirUrl, import, 1, errors)) + return false; + } else { + const QString qmldirFilePath = QQmlFile::urlToLocalFileOrQrc(qmldirUrl); + if (!loadImportDependencies(import, qmldirFilePath, import->flags, errors)) + return false; + + postProcessQmldir(this, import, qmldirFilePath, qmldirUrl); + } + + return true; } -bool QQmlTypeLoader::Blob::addImport(QQmlTypeLoader::Blob::PendingImportPtr import, QList<QQmlError> *errors) +static void addDependencyImportError( + const QQmlTypeLoader::Blob::PendingImportPtr &import, QList<QQmlError> *errors) { - Q_ASSERT(errors); + QQmlError error; + QString reason = errors->front().description(); + if (reason.size() > 512) + reason = reason.first(252) + QLatin1String("... ...") + reason.last(252); + if (import->version.hasMajorVersion()) { + error.setDescription(QQmlImportDatabase::tr( + "module \"%1\" version %2.%3 cannot be imported because:\n%4") + .arg(import->uri).arg(import->version.majorVersion()) + .arg(import->version.hasMinorVersion() + ? QString::number(import->version.minorVersion()) + : QLatin1String("x")) + .arg(reason)); + } else { + error.setDescription(QQmlImportDatabase::tr("module \"%1\" cannot be imported because:\n%2") + .arg(import->uri, reason)); + } + errors->prepend(error); +} +bool QQmlTypeLoader::Blob::addLibraryImport(const QQmlTypeLoader::Blob::PendingImportPtr &import, QList<QQmlError> *errors) +{ QQmlImportDatabase *importDatabase = typeLoader()->importDatabase(); - if (import->type == QV4::CompiledData::Import::ImportScript) { - QUrl scriptUrl = finalUrl().resolved(QUrl(import->uri)); - QQmlRefPointer<QQmlScriptBlob> blob = typeLoader()->getScript(scriptUrl); - addDependency(blob.data()); + const QQmlImportDatabase::LocalQmldirSearchLocation searchMode = + QQmlMetaType::isStronglyLockedModule(import->uri, import->version) + ? QQmlImportDatabase::QmldirCacheOnly + : QQmlImportDatabase::QmldirFileAndCache; + + const QQmlImportDatabase::LocalQmldirResult qmldirResult + = importDatabase->locateLocalQmldir( + import->uri, import->version, searchMode, + [&](const QString &qmldirFilePath, const QString &qmldirUrl) { + // This is a local library import + const QTypeRevision actualVersion = m_importCache->addLibraryImport( + typeLoader(), import->uri, import->qualifier, import->version, qmldirFilePath, + qmldirUrl, import->flags, import->precedence, errors); + if (!actualVersion.isValid()) + return false; - scriptImported(blob, import->location, import->qualifier, QString()); - } else if (import->type == QV4::CompiledData::Import::ImportLibrary) { - QString qmldirFilePath; - QString qmldirUrl; + // Use more specific version for dependencies if possible + if (actualVersion.hasMajorVersion()) + import->version = actualVersion; - if (m_importCache.locateQmldir(importDatabase, import->uri, import->majorVersion, import->minorVersion, - &qmldirFilePath, &qmldirUrl)) { - // This is a local library import - if (!m_importCache.addLibraryImport(importDatabase, import->uri, import->qualifier, import->majorVersion, - import->minorVersion, qmldirFilePath, qmldirUrl, false, errors)) - return false; + if (!loadImportDependencies(import, qmldirFilePath, import->flags, errors)) { + addDependencyImportError(import, errors); + return false; + } - if (!loadImportDependencies(import, qmldirFilePath, errors)) - return false; + postProcessQmldir(this, import, qmldirFilePath, qmldirUrl); + return true; + }); - if (!import->qualifier.isEmpty()) { - // Does this library contain any qualified scripts? - QUrl libraryUrl(qmldirUrl); - const QQmlTypeLoaderQmldirContent qmldir = typeLoader()->qmldirContent(qmldirFilePath); - const auto qmldirScripts = qmldir.scripts(); - for (const QQmlDirParser::Script &script : qmldirScripts) { - QUrl scriptUrl = libraryUrl.resolved(QUrl(script.fileName)); - QQmlRefPointer<QQmlScriptBlob> blob = typeLoader()->getScript(scriptUrl); - addDependency(blob.data()); - - scriptImported(blob, import->location, script.nameSpace, import->qualifier); - } - } - } else { - // Is this a module? - if (QQmlMetaType::isAnyModule(import->uri)) { - if (!m_importCache.addLibraryImport(importDatabase, import->uri, import->qualifier, import->majorVersion, - import->minorVersion, QString(), QString(), false, errors)) - return false; - } else { - // We haven't yet resolved this import - m_unresolvedImports << import; - - QQmlAbstractUrlInterceptor *interceptor = typeLoader()->engine()->urlInterceptor(); - - // Query any network import paths for this library. - // Interceptor might redirect local paths. - QStringList remotePathList = importDatabase->importPathList( - interceptor ? QQmlImportDatabase::LocalOrRemote - : QQmlImportDatabase::Remote); - if (!remotePathList.isEmpty()) { - // Add this library and request the possible locations for it - if (!m_importCache.addLibraryImport(importDatabase, import->uri, import->qualifier, import->majorVersion, - import->minorVersion, QString(), QString(), true, errors)) - return false; + switch (qmldirResult) { + case QQmlImportDatabase::QmldirFound: + return true; + case QQmlImportDatabase::QmldirNotFound: { + if (!loadImportDependencies(import, QString(), import->flags, errors)) { + addDependencyImportError(import, errors); + return false; + } + break; + } + case QQmlImportDatabase::QmldirInterceptedToRemote: + break; + case QQmlImportDatabase::QmldirRejected: + return false; + } - // Probe for all possible locations - int priority = 0; - const QStringList qmlDirPaths = QQmlImports::completeQmldirPaths(import->uri, remotePathList, import->majorVersion, import->minorVersion); - for (const QString &qmldirPath : qmlDirPaths) { - if (interceptor) { - QUrl url = interceptor->intercept( - QQmlImports::urlFromLocalFileOrQrcOrUrl(qmldirPath), - QQmlAbstractUrlInterceptor::QmldirFile); - if (!QQmlFile::isLocalFile(url) - && !fetchQmldir(url, import, ++priority, errors)) { - return false; - } - } else if (!fetchQmldir(QUrl(qmldirPath), import, ++priority, errors)) { - return false; - } + // If there is a qmldir we cannot see, yet, then we have to wait. + // The qmldir might contain import directives. + if (qmldirResult != QQmlImportDatabase::QmldirInterceptedToRemote && ( + // Major version of module already registered: + // We believe that the registration is complete. + QQmlMetaType::typeModule(import->uri, import->version) - } - } - } + // Otherwise, try to register further module types. + || QQmlMetaType::qmlRegisterModuleTypes(import->uri) + + // Otherwise, there is no way to register any further types. + // Try with any module of that name. + || QQmlMetaType::latestModuleVersion(import->uri).isValid())) { + + if (!m_importCache->addLibraryImport( + typeLoader(), import->uri, import->qualifier, import->version, QString(), + QString(), import->flags, import->precedence, errors).isValid()) { + return false; } } else { - Q_ASSERT(import->type == QV4::CompiledData::Import::ImportFile); - - bool incomplete = false; - - QUrl importUrl(import->uri); - QString path = importUrl.path(); - path.append(QLatin1String(path.endsWith(QLatin1Char('/')) ? "qmldir" : "/qmldir")); - importUrl.setPath(path); - QUrl qmldirUrl = finalUrl().resolved(importUrl); - if (!QQmlImports::isLocal(qmldirUrl)) { - // This is a remote file; the import is currently incomplete - incomplete = true; - } + // We haven't yet resolved this import + m_unresolvedImports << import; + + const QQmlEngine *engine = typeLoader()->engine(); + const bool hasInterceptors + = !(QQmlEnginePrivate::get(engine)->urlInterceptors.isEmpty()); + + // Query any network import paths for this library. + // Interceptor might redirect local paths. + QStringList remotePathList = importDatabase->importPathList( + hasInterceptors ? QQmlImportDatabase::LocalOrRemote + : QQmlImportDatabase::Remote); + if (!remotePathList.isEmpty()) { + // Add this library and request the possible locations for it + const QTypeRevision version = m_importCache->addLibraryImport( + typeLoader(), import->uri, import->qualifier, import->version, QString(), + QString(), import->flags | QQmlImports::ImportIncomplete, import->precedence, + errors); + + if (!version.isValid()) + return false; - if (!m_importCache.addFileImport(importDatabase, import->uri, import->qualifier, import->majorVersion, - import->minorVersion, incomplete, errors)) - return false; + // Use more specific version for finding the qmldir if possible + if (version.hasMajorVersion()) + import->version = version; + + // Probe for all possible locations + int priority = 0; + const QStringList qmlDirPaths = QQmlImports::completeQmldirPaths( + import->uri, remotePathList, import->version); + for (const QString &qmldirPath : qmlDirPaths) { + if (hasInterceptors) { + QUrl url = engine->interceptUrl( + QQmlImports::urlFromLocalFileOrQrcOrUrl(qmldirPath), + QQmlAbstractUrlInterceptor::QmldirFile); + if (!QQmlFile::isLocalFile(url) + && !fetchQmldir(url, import, ++priority, errors)) { + return false; + } + } else if (!fetchQmldir(QUrl(qmldirPath), import, ++priority, errors)) { + return false; + } - if (incomplete) { - if (!fetchQmldir(qmldirUrl, import, 1, errors)) - return false; + } } } return true; } +bool QQmlTypeLoader::Blob::addImport(const QV4::CompiledData::Import *import, + QQmlImports::ImportFlags flags, QList<QQmlError> *errors) +{ + return addImport(std::make_shared<PendingImport>(this, import, flags), errors); +} + +bool QQmlTypeLoader::Blob::addImport( + QQmlTypeLoader::Blob::PendingImportPtr import, QList<QQmlError> *errors) +{ + Q_ASSERT(errors); + + switch (import->type) + { + case QV4::CompiledData::Import::ImportLibrary: + return addLibraryImport(import, errors); + case QV4::CompiledData::Import::ImportFile: + return addFileImport(import ,errors); + case QV4::CompiledData::Import::ImportScript: + return addScriptImport(import); + case QV4::CompiledData::Import::ImportInlineComponent: + Q_UNREACHABLE_RETURN(false); // addImport is never called with an inline component import + } + + Q_UNREACHABLE_RETURN(false); +} + void QQmlTypeLoader::Blob::dependencyComplete(QQmlDataBlob *blob) { if (blob->type() == QQmlDataBlob::QmldirFile) { QQmlQmldirData *data = static_cast<QQmlQmldirData *>(blob); - - PendingImportPtr import = data->import(this); - QList<QQmlError> errors; if (!qmldirDataAvailable(data, &errors)) { Q_ASSERT(errors.size()); QQmlError error(errors.takeFirst()); - error.setUrl(m_importCache.baseUrl()); - error.setLine(import->location.line); - error.setColumn(import->location.column); + error.setUrl(m_importCache->baseUrl()); + const QV4::CompiledData::Location importLocation = data->importLocation(this); + error.setLine(qmlConvertSourceCoordinate<quint32, int>(importLocation.line())); + error.setColumn(qmlConvertSourceCoordinate<quint32, int>(importLocation.column())); errors.prepend(error); // put it back on the list after filling out information. setError(errors); } } } -bool QQmlTypeLoader::Blob::loadImportDependencies(PendingImportPtr currentImport, const QString &qmldirUri, QList<QQmlError> *errors) +bool QQmlTypeLoader::Blob::loadDependentImports( + const QList<QQmlDirParser::Import> &imports, const QString &qualifier, + QTypeRevision version, quint16 precedence, QQmlImports::ImportFlags flags, + QList<QQmlError> *errors) { - const QQmlTypeLoaderQmldirContent qmldir = typeLoader()->qmldirContent(qmldirUri); - for (const QString &implicitImports: qmldir.imports()) { + for (const auto &import : imports) { + if (import.flags & QQmlDirParser::Import::Optional) + continue; auto dependencyImport = std::make_shared<PendingImport>(); - dependencyImport->uri = implicitImports; - dependencyImport->qualifier = currentImport->qualifier; - dependencyImport->majorVersion = currentImport->majorVersion; - dependencyImport->minorVersion = currentImport->minorVersion; - if (!addImport(dependencyImport, errors)) + dependencyImport->uri = import.module; + dependencyImport->qualifier = qualifier; + dependencyImport->version = (import.flags & QQmlDirParser::Import::Auto) + ? version : import.version; + dependencyImport->flags = flags; + dependencyImport->precedence = precedence; + + qCDebug(lcQmlImport) + << "loading dependent import" << dependencyImport->uri << "version" + << dependencyImport->version << "as" << dependencyImport->qualifier; + + if (!addImport(dependencyImport, errors)) { + QQmlError error; + error.setDescription( + QString::fromLatin1( + "Failed to load dependent import \"%1\" version %2.%3") + .arg(dependencyImport->uri) + .arg(dependencyImport->version.majorVersion()) + .arg(dependencyImport->version.minorVersion())); + errors->append(error); return false; + } } + + return true; +} + +bool QQmlTypeLoader::Blob::loadImportDependencies( + const QQmlTypeLoader::Blob::PendingImportPtr ¤tImport, const QString &qmldirUri, + QQmlImports::ImportFlags flags, QList<QQmlError> *errors) +{ + QList<QQmlDirParser::Import> implicitImports + = QQmlMetaType::moduleImports(currentImport->uri, currentImport->version); + if (!qmldirUri.isEmpty()) + implicitImports += typeLoader()->qmldirContent(qmldirUri).imports(); + + // Prevent overflow from one category of import into the other. + switch (currentImport->precedence) { + case QQmlImportInstance::Implicit - 1: + case QQmlImportInstance::Lowest: { + QQmlError error; + error.setDescription( + QString::fromLatin1("Too many dependent imports for %1 %2.%3") + .arg(currentImport->uri) + .arg(currentImport->version.majorVersion()) + .arg(currentImport->version.minorVersion())); + errors->append(error); + return false; + } + default: + break; + } + + if (!loadDependentImports( + implicitImports, currentImport->qualifier, currentImport->version, + currentImport->precedence + 1, flags, errors)) { + QQmlError error; + error.setDescription( + QString::fromLatin1( + "Failed to load dependencies for module \"%1\" version %2.%3") + .arg(currentImport->uri) + .arg(currentImport->version.majorVersion()) + .arg(currentImport->version.minorVersion())); + errors->append(error); + return false; + } + return true; } @@ -707,40 +871,34 @@ bool QQmlTypeLoader::Blob::isDebugging() const return typeLoader()->engine()->handle()->debugger() != nullptr; } -bool QQmlTypeLoader::Blob::diskCacheDisabled() +bool QQmlTypeLoader::Blob::readCacheFile() const { - return disableDiskCache(); + return typeLoader()->engine()->handle()->diskCacheOptions() + & QV4::ExecutionEngine::DiskCache::QmlcRead; } -bool QQmlTypeLoader::Blob::diskCacheForced() +bool QQmlTypeLoader::Blob::writeCacheFile() const { - return forceDiskCache(); + return typeLoader()->engine()->handle()->diskCacheOptions() + & QV4::ExecutionEngine::DiskCache::QmlcWrite; } -bool QQmlTypeLoader::Blob::qmldirDataAvailable(const QQmlRefPointer<QQmlQmldirData> &data, QList<QQmlError> *errors) +QQmlMetaType::CacheMode QQmlTypeLoader::Blob::aotCacheMode() const { - PendingImportPtr import = data->import(this); - data->setImport(this, nullptr); - - int priority = data->priority(this); - data->setPriority(this, 0); - - if (import) { - // Do we need to resolve this import? - const bool resolve = (import->priority == 0) || (import->priority > priority); - - if (resolve) { - // This is the (current) best resolution for this import - if (!updateQmldir(data, import, errors)) { - return false; - } - - import->priority = priority; - return true; - } - } + const QV4::ExecutionEngine::DiskCacheOptions options + = typeLoader()->engine()->handle()->diskCacheOptions(); + if (!(options & QV4::ExecutionEngine::DiskCache::Aot)) + return QQmlMetaType::RejectAll; + if (options & QV4::ExecutionEngine::DiskCache::AotByteCode) + return QQmlMetaType::AcceptUntyped; + return QQmlMetaType::RequireFullyTyped; +} - return true; +bool QQmlTypeLoader::Blob::qmldirDataAvailable(const QQmlRefPointer<QQmlQmldirData> &data, QList<QQmlError> *errors) +{ + return data->processImports(this, [&](PendingImportPtr import) { + return updateQmldir(data, import, errors); + }); } /*! @@ -805,7 +963,11 @@ QQmlRefPointer<QQmlTypeData> QQmlTypeLoader::getType(const QUrl &unNormalizedUrl // TODO: if (compiledData == 0), is it safe to omit this insertion? m_typeCache.insert(url, typeData); QQmlMetaType::CachedUnitLookupError error = QQmlMetaType::CachedUnitLookupError::NoError; - if (const QV4::CompiledData::Unit *cachedUnit = QQmlMetaType::findCachedCompilationUnit(typeData->url(), &error)) { + + const QQmlMetaType::CacheMode cacheMode = typeData->aotCacheMode(); + if (const QQmlPrivate::CachedQmlUnit *cachedUnit = (cacheMode != QQmlMetaType::RejectAll) + ? QQmlMetaType::findCachedCompilationUnit(typeData->url(), cacheMode, &error) + : nullptr) { QQmlTypeLoader::loadWithCachedUnit(typeData, cachedUnit, mode); } else { typeData->setCachedUnitStatus(error); @@ -815,16 +977,16 @@ QQmlRefPointer<QQmlTypeData> QQmlTypeLoader::getType(const QUrl &unNormalizedUrl // this was started Asynchronous, but we need to force Synchronous // completion now (if at all possible with this type of URL). +#if QT_CONFIG(thread) if (!m_thread->isThisThread()) { // this only works when called directly from the UI thread, but not // when recursively called on the QML thread via resolveTypes() while (!typeData->isCompleteOrError()) { - unlock(); m_thread->waitForNextMessage(); - lock(); } } +#endif } return typeData; @@ -844,6 +1006,26 @@ QQmlRefPointer<QQmlTypeData> QQmlTypeLoader::getType(const QByteArray &data, con return QQmlRefPointer<QQmlTypeData>(typeData, QQmlRefPointer<QQmlTypeData>::Adopt); } +void QQmlTypeLoader::injectScript(const QUrl &relativeUrl, const QV4::Value &value) +{ + LockHolder<QQmlTypeLoader> holder(this); + + QQmlScriptBlob *blob = new QQmlScriptBlob(relativeUrl, this); + blob->initializeFromNative(value); + blob->m_isDone = true; + blob->m_data.setStatus(QQmlDataBlob::Complete); + m_scriptCache.insert(relativeUrl, blob); +} + +QQmlRefPointer<QQmlScriptBlob> QQmlTypeLoader::injectedScript(const QUrl &relativeUrl) +{ + LockHolder<QQmlTypeLoader> holder(this); + const auto it = m_scriptCache.constFind(relativeUrl); + return (it != m_scriptCache.constEnd() && (*it)->isNative()) + ? *it + : QQmlRefPointer<QQmlScriptBlob>(); +} + /*! Return a QQmlScriptBlob for \a url. The QQmlScriptData may be cached. */ @@ -863,8 +1045,11 @@ QQmlRefPointer<QQmlScriptBlob> QQmlTypeLoader::getScript(const QUrl &unNormalize scriptBlob = new QQmlScriptBlob(url, this); m_scriptCache.insert(url, scriptBlob); - QQmlMetaType::CachedUnitLookupError error; - if (const QV4::CompiledData::Unit *cachedUnit = QQmlMetaType::findCachedCompilationUnit(scriptBlob->url(), &error)) { + QQmlMetaType::CachedUnitLookupError error = QQmlMetaType::CachedUnitLookupError::NoError; + const QQmlMetaType::CacheMode cacheMode = scriptBlob->aotCacheMode(); + if (const QQmlPrivate::CachedQmlUnit *cachedUnit = (cacheMode != QQmlMetaType::RejectAll) + ? QQmlMetaType::findCachedCompilationUnit(scriptBlob->url(), cacheMode, &error) + : nullptr) { QQmlTypeLoader::loadWithCachedUnit(scriptBlob, cachedUnit); } else { scriptBlob->setCachedUnitStatus(error); @@ -913,19 +1098,19 @@ QString QQmlTypeLoader::absoluteFilePath(const QString &path) // qrc resource QFileInfo fileInfo(path); return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString(); - } else if (path.count() > 3 && path.at(3) == QLatin1Char(':') && + } else if (path.size() > 3 && path.at(3) == QLatin1Char(':') && path.startsWith(QLatin1String("qrc"), Qt::CaseInsensitive)) { // qrc resource url QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path)); return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString(); } #if defined(Q_OS_ANDROID) - else if (path.count() > 7 && path.at(6) == QLatin1Char(':') && path.at(7) == QLatin1Char('/') && + else if (path.size() > 7 && path.at(6) == QLatin1Char(':') && path.at(7) == QLatin1Char('/') && path.startsWith(QLatin1String("assets"), Qt::CaseInsensitive)) { // assets resource url QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path)); return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString(); - } else if (path.count() > 8 && path.at(7) == QLatin1Char(':') && path.at(8) == QLatin1Char('/') && + } else if (path.size() > 8 && path.at(7) == QLatin1Char(':') && path.at(8) == QLatin1Char('/') && path.startsWith(QLatin1String("content"), Qt::CaseInsensitive)) { // content url QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path)); @@ -947,7 +1132,7 @@ QString QQmlTypeLoader::absoluteFilePath(const QString &path) return QString(); QString absoluteFilePath; - QString fileName(path.mid(lastSlash+1, path.length()-lastSlash-1)); + QString fileName(path.mid(lastSlash+1, path.size()-lastSlash-1)); bool *value = fileSet->object(fileName); if (value) { @@ -960,7 +1145,7 @@ QString QQmlTypeLoader::absoluteFilePath(const QString &path) absoluteFilePath = path; } - if (absoluteFilePath.length() > 2 && absoluteFilePath.at(0) != QLatin1Char('/') && absoluteFilePath.at(1) != QLatin1Char(':')) + if (absoluteFilePath.size() > 2 && absoluteFilePath.at(0) != QLatin1Char('/') && absoluteFilePath.at(1) != QLatin1Char(':')) absoluteFilePath = QFileInfo(absoluteFilePath).absoluteFilePath(); return absoluteFilePath; @@ -973,48 +1158,56 @@ bool QQmlTypeLoader::fileExists(const QString &path, const QString &file) return false; Q_ASSERT(path.endsWith(QLatin1Char('/'))); + + LockHolder<QQmlTypeLoader> holder(this); + QCache<QString, bool> *fileSet = m_importDirCache.object(path); + if (fileSet) { + if (bool *value = fileSet->object(file)) + return *value; + } else if (m_importDirCache.contains(path)) { + // explicit nullptr in cache + return false; + } + + auto addToCache = [&](const QFileInfo &fileInfo) { + if (!fileSet) { + fileSet = fileInfo.dir().exists() ? new QCache<QString, bool> : nullptr; + m_importDirCache.insert(path, fileSet); + if (!fileSet) + return false; + } + + const bool exists = fileInfo.exists(); + fileSet->insert(file, new bool(exists)); + return exists; + }; + if (path.at(0) == QLatin1Char(':')) { // qrc resource - QFileInfo fileInfo(path + file); - return fileInfo.isFile(); - } else if (path.count() > 3 && path.at(3) == QLatin1Char(':') && - path.startsWith(QLatin1String("qrc"), Qt::CaseInsensitive)) { + return addToCache(QFileInfo(path + file)); + } + + if (path.size() > 3 && path.at(3) == QLatin1Char(':') + && path.startsWith(QLatin1String("qrc"), Qt::CaseInsensitive)) { // qrc resource url - QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path + file)); - return fileInfo.isFile(); + return addToCache(QFileInfo(QQmlFile::urlToLocalFileOrQrc(path + file))); } + #if defined(Q_OS_ANDROID) - else if (path.count() > 7 && path.at(6) == QLatin1Char(':') && path.at(7) == QLatin1Char('/') && - path.startsWith(QLatin1String("assets"), Qt::CaseInsensitive)) { + if (path.size() > 7 && path.at(6) == QLatin1Char(':') && path.at(7) == QLatin1Char('/') + && path.startsWith(QLatin1String("assets"), Qt::CaseInsensitive)) { // assets resource url - QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path + file)); - return fileInfo.isFile(); - } else if (path.count() > 8 && path.at(7) == QLatin1Char(':') && path.at(8) == QLatin1Char('/') && - path.startsWith(QLatin1String("content"), Qt::CaseInsensitive)) { - // content url - QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path + file)); - return fileInfo.isFile(); + return addToCache(QFileInfo(QQmlFile::urlToLocalFileOrQrc(path + file))); } -#endif - LockHolder<QQmlTypeLoader> holder(this); - if (!m_importDirCache.contains(path)) { - bool exists = QDir(path).exists(); - QCache<QString, bool> *entry = exists ? new QCache<QString, bool> : nullptr; - m_importDirCache.insert(path, entry); + if (path.size() > 8 && path.at(7) == QLatin1Char(':') && path.at(8) == QLatin1Char('/') + && path.startsWith(QLatin1String("content"), Qt::CaseInsensitive)) { + // content url + return addToCache(QFileInfo(QQmlFile::urlToLocalFileOrQrc(path + file))); } - QCache<QString, bool> *fileSet = m_importDirCache.object(path); - if (!fileSet) - return false; +#endif - bool *value = fileSet->object(file); - if (value) { - return *value; - } else { - bool exists = QFile::exists(path + file); - fileSet->insert(file, new bool(exists)); - return exists; - } + return addToCache(QFileInfo(path + file)); } @@ -1038,7 +1231,7 @@ bool QQmlTypeLoader::directoryExists(const QString &path) return fileInfo.exists() && fileInfo.isDir(); } - int length = path.length(); + int length = path.size(); if (path.endsWith(QLatin1Char('/'))) --length; QString dirPath(path.left(length)); @@ -1075,7 +1268,7 @@ const QQmlTypeLoaderQmldirContent QQmlTypeLoader::qmldirContent(const QString &f // Yet, this heuristic is the best we can do until we pass more structured information here, // for example a QUrl also for local files. QUrl url(filePathIn); - if (url.scheme().length() < 2) { + if (url.scheme().size() < 2) { filePath = filePathIn; } else { filePath = QQmlFile::urlToLocalFileOrQrc(url); @@ -1125,7 +1318,8 @@ void QQmlTypeLoader::setQmldirContent(const QString &url, const QString &content m_importQmlDirCache.insert(url, qmldir); } - qmldir->setContent(url, content); + if (!qmldir->hasContent()) + qmldir->setContent(url, content); } /*! @@ -1134,6 +1328,11 @@ and qmldir information. */ void QQmlTypeLoader::clearCache() { + // Pending messages typically hold references to the blobs they want to be delivered to. + // We don't want them anymore. + if (m_thread) + m_thread->discardMessages(); + for (TypeCache::Iterator iter = m_typeCache.begin(), end = m_typeCache.end(); iter != end; ++iter) (*iter)->release(); for (ScriptCache::Iterator iter = m_scriptCache.begin(), end = m_scriptCache.end(); iter != end; ++iter) @@ -1149,7 +1348,7 @@ void QQmlTypeLoader::clearCache() m_qmldirCache.clear(); m_importDirCache.clear(); m_importQmlDirCache.clear(); - QQmlMetaType::freeUnusedTypesAndCaches(); + m_checksumCache.clear(); } void QQmlTypeLoader::updateTypeCacheTrimThreshold() @@ -1164,29 +1363,40 @@ void QQmlTypeLoader::updateTypeCacheTrimThreshold() void QQmlTypeLoader::trimCache() { while (true) { - QList<TypeCache::Iterator> unneededTypes; - for (TypeCache::Iterator iter = m_typeCache.begin(), end = m_typeCache.end(); iter != end; ++iter) { + bool deletedOneType = false; + for (TypeCache::Iterator iter = m_typeCache.begin(), end = m_typeCache.end(); iter != end;) { QQmlTypeData *typeData = iter.value(); // typeData->m_compiledData may be set early on in the proccess of loading a file, so // it's important to check the general loading status of the typeData before making any // other decisions. - if (typeData->count() == 1 && (typeData->isError() || typeData->isComplete()) - && (!typeData->m_compiledData || typeData->m_compiledData->count() == 1)) { - // There are no live objects of this type - unneededTypes.append(iter); + if (typeData->count() != 1 || (!typeData->isError() && !typeData->isComplete())) { + ++iter; + continue; } - } - if (unneededTypes.isEmpty()) - break; + const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit + = typeData->m_compiledData; + if (compilationUnit) { + if (compilationUnit->count() + > QQmlMetaType::countInternalCompositeTypeSelfReferences( + compilationUnit) + 1) { + ++iter; + continue; + } - while (!unneededTypes.isEmpty()) { - TypeCache::Iterator iter = unneededTypes.takeLast(); + QQmlMetaType::unregisterInternalCompositeType(compilationUnit); + Q_ASSERT(compilationUnit->count() == 1); + } + // There are no live objects of this type iter.value()->release(); - m_typeCache.erase(iter); + iter = m_typeCache.erase(iter); + deletedOneType = true; } + + if (!deletedOneType) + break; } updateTypeCacheTrimThreshold(); |