diff options
-rw-r--r-- | src/qml/qml/qqmltypedata.cpp | 35 | ||||
-rw-r--r-- | src/qml/qml/qqmltypeloader.cpp | 124 | ||||
-rw-r--r-- | src/qml/qml/qqmltypeloader_p.h | 3 | ||||
-rw-r--r-- | tests/auto/qml/qqmlmoduleplugin/data/importsNested.1.errors.txt | 1 | ||||
-rw-r--r-- | tests/auto/qml/qqmlmoduleplugin/data/importsNested.1.qml | 3 | ||||
-rw-r--r-- | tests/auto/qml/qqmlmoduleplugin/nestedPlugin/nestedPlugin.cpp | 2 | ||||
-rw-r--r-- | tests/auto/qml/qqmlmoduleplugin/tst_qqmlmoduleplugin.cpp | 22 | ||||
-rw-r--r-- | tests/auto/qml/qqmltypeloader/data/qobjectSingletonUser.qml | 7 | ||||
-rw-r--r-- | tests/auto/qml/qqmltypeloader/tst_qqmltypeloader.cpp | 61 |
9 files changed, 182 insertions, 76 deletions
diff --git a/src/qml/qml/qqmltypedata.cpp b/src/qml/qml/qqmltypedata.cpp index c7de010fcd..86b6de63b3 100644 --- a/src/qml/qml/qqmltypedata.cpp +++ b/src/qml/qml/qqmltypedata.cpp @@ -798,22 +798,25 @@ void QQmlTypeData::allDependenciesDone() QList<QQmlError> errors; auto it = m_unresolvedImports.constBegin(), end = m_unresolvedImports.constEnd(); for ( ; it != end; ++it) { - if ((*it)->priority == 0) { - // This import was not resolved - for (auto keyIt = m_unresolvedImports.constBegin(), - keyEnd = m_unresolvedImports.constEnd(); - keyIt != keyEnd; ++keyIt) { - const PendingImportPtr &import = *keyIt; - QQmlError error; - error.setDescription(QQmlTypeLoader::tr("module \"%1\" is not installed").arg(import->uri)); - error.setUrl(m_importCache->baseUrl()); - error.setLine(qmlConvertSourceCoordinate<quint32, int>( - import->location.line())); - error.setColumn(qmlConvertSourceCoordinate<quint32, int>( - import->location.column())); - errors.prepend(error); - } - } + const PendingImportPtr &import = *it; + if (import->priority != 0) + continue; + + // If the import was potentially remote and all the network requests have failed, + // we now know that there is no qmldir. We can register its types. + if (registerPendingTypes(import)) + continue; + + // This import was not resolved + QQmlError error; + error.setDescription(QQmlTypeLoader::tr("module \"%1\" is not installed") + .arg(import->uri)); + error.setUrl(m_importCache->baseUrl()); + error.setLine(qmlConvertSourceCoordinate<quint32, int>( + import->location.line())); + error.setColumn(qmlConvertSourceCoordinate<quint32, int>( + import->location.column())); + errors.prepend(error); } if (errors.size()) { setError(errors); diff --git a/src/qml/qml/qqmltypeloader.cpp b/src/qml/qml/qqmltypeloader.cpp index 89199c8652..7420a1654c 100644 --- a/src/qml/qml/qqmltypeloader.cpp +++ b/src/qml/qml/qqmltypeloader.cpp @@ -925,64 +925,57 @@ bool QQmlTypeLoader::Blob::addLibraryImport(const QQmlTypeLoader::Blob::PendingI // If there is a qmldir we cannot see, yet, then we have to wait. // The qmldir might contain import directives. - if (qmldirResult != QmldirInterceptedToRemote && ( - // Major version of module already registered: - // We believe that the registration is complete. - QQmlMetaType::typeModule(import->uri, import->version) + // TODO: This should trigger on any potentially remote URLs, not only intercepted ones. + // However, fixing this would open the door for follow-up problems while providing + // rather limited benefits. + if (qmldirResult != QmldirInterceptedToRemote && registerPendingTypes(import)) { + if (m_importCache->addLibraryImport( + typeLoader(), import->uri, import->qualifier, import->version, QString(), + QString(), import->flags, import->precedence, errors).isValid()) { + return true; + } - // Otherwise, try to register further module types. - || QQmlMetaType::qmlRegisterModuleTypes(import->uri) + return false; + } - // Otherwise, there is no way to register any further types. - // Try with any module of that name. - || QQmlMetaType::latestModuleVersion(import->uri).isValid())) { + // We haven't yet resolved this import + m_unresolvedImports << import; - if (!m_importCache->addLibraryImport( - typeLoader(), import->uri, import->qualifier, import->version, QString(), - QString(), import->flags, import->precedence, errors).isValid()) { - return false; - } - } else { - // We haven't yet resolved this import - m_unresolvedImports << import; - - const bool hasInterceptors = m_typeLoader->hasUrlInterceptors(); - - // Query any network import paths for this library. - // Interceptor might redirect local paths. - QStringList remotePathList = typeLoader()->importPathList( - hasInterceptors ? LocalOrRemote : 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; + // 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); - // 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 = m_typeLoader->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)) { + if (!version.isValid()) + return false; + + // Use more specific version for finding the qmldir if possible + if (version.hasMajorVersion()) + import->version = version; + + const bool hasInterceptors = m_typeLoader->hasUrlInterceptors(); + + // Query any network import paths for this library. + // Interceptor might redirect local paths. + QStringList remotePathList = typeLoader()->importPathList( + hasInterceptors ? LocalOrRemote : Remote); + if (!remotePathList.isEmpty()) { + // 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 = m_typeLoader->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; } } } @@ -990,6 +983,23 @@ bool QQmlTypeLoader::Blob::addLibraryImport(const QQmlTypeLoader::Blob::PendingI return true; } +bool QQmlTypeLoader::Blob::registerPendingTypes(const PendingImportPtr &import) +{ + assertTypeLoaderThread(); + + return + // 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(); +} + bool QQmlTypeLoader::Blob::addImport(const QV4::CompiledData::Import *import, QQmlImports::ImportFlags flags, QList<QQmlError> *errors) { @@ -1883,6 +1893,16 @@ QQmlTypeLoader::LocalQmldirResult QQmlTypeLoader::locateLocalQmldir( QString qmldirAbsoluteFilePath; for (QString qmldirPath : qmlDirPaths) { if (hasInterceptors) { + // TODO: + // 1. This is inexact. It triggers only on the existence of interceptors, not on + // actual interception. If the URL was remote to begin with but no interceptor + // actually changes it, we still clear the qmldirPath and consider it + // QmldirInterceptedToRemote. + // 2. This misdiagnosis makes addLibraryImport do the right thing and postpone + // the loading of pre-registered types for any QML engine that has interceptors + // (even if they don't do anything in this case). + // Fixing this would open the door to follow-up problems but wouldn't result in any + // significant benefit. const QUrl intercepted = doInterceptUrl( QQmlImports::urlFromLocalFileOrQrcOrUrl(qmldirPath), QQmlAbstractUrlInterceptor::QmldirFile, diff --git a/src/qml/qml/qqmltypeloader_p.h b/src/qml/qml/qqmltypeloader_p.h index c59b0069e0..47df026a07 100644 --- a/src/qml/qml/qqmltypeloader_p.h +++ b/src/qml/qml/qqmltypeloader_p.h @@ -114,6 +114,9 @@ public: QQmlImports::ImportFlags flags, QList<QQmlError> *errors); protected: + + bool registerPendingTypes(const PendingImportPtr &import); + bool loadDependentImports( const QList<QQmlDirParser::Import> &imports, const QString &qualifier, QTypeRevision version, quint16 precedence, QQmlImports::ImportFlags flags, diff --git a/tests/auto/qml/qqmlmoduleplugin/data/importsNested.1.errors.txt b/tests/auto/qml/qqmlmoduleplugin/data/importsNested.1.errors.txt deleted file mode 100644 index de75f47c03..0000000000 --- a/tests/auto/qml/qqmlmoduleplugin/data/importsNested.1.errors.txt +++ /dev/null @@ -1 +0,0 @@ -1:1:module "org.qtproject.AutoTestQmlNestedPluginType.Nested" is not installed diff --git a/tests/auto/qml/qqmlmoduleplugin/data/importsNested.1.qml b/tests/auto/qml/qqmlmoduleplugin/data/importsNested.1.qml index 35fff29a69..5148653a53 100644 --- a/tests/auto/qml/qqmlmoduleplugin/data/importsNested.1.qml +++ b/tests/auto/qml/qqmlmoduleplugin/data/importsNested.1.qml @@ -1,5 +1,8 @@ import org.qtproject.AutoTestQmlNestedPluginType.Nested 1.0 import org.qtproject.AutoTestQmlNestedPluginType 1.0 +import QtQml MyNestedPluginType { + property Conflict conflict: Conflict {} + Component.onCompleted: console.log(conflict.value) } diff --git a/tests/auto/qml/qqmlmoduleplugin/nestedPlugin/nestedPlugin.cpp b/tests/auto/qml/qqmlmoduleplugin/nestedPlugin/nestedPlugin.cpp index 9bddf0f0c4..ed03894fe3 100644 --- a/tests/auto/qml/qqmlmoduleplugin/nestedPlugin/nestedPlugin.cpp +++ b/tests/auto/qml/qqmlmoduleplugin/nestedPlugin/nestedPlugin.cpp @@ -33,11 +33,13 @@ public: { Q_ASSERT(QLatin1String(uri) == "org.qtproject.AutoTestQmlNestedPluginType"); qmlRegisterType<MyPluginTypeNested>(uri, 1, 0, "MyPluginType"); + qmlRegisterType<MyPluginTypeNested>(uri, 1, 0, "Conflict"); QString nestedUri(uri); nestedUri += QLatin1String(".Nested"); qmlRegisterType<MyNestedPluginType>(nestedUri.toLatin1().constData(), 1, 0, "MyNestedPluginType"); + qmlRegisterType<MyNestedPluginType>(nestedUri.toLatin1().constData(), 1, 0, "Conflict"); } }; diff --git a/tests/auto/qml/qqmlmoduleplugin/tst_qqmlmoduleplugin.cpp b/tests/auto/qml/qqmlmoduleplugin/tst_qqmlmoduleplugin.cpp index 832642d22a..35e71612be 100644 --- a/tests/auto/qml/qqmlmoduleplugin/tst_qqmlmoduleplugin.cpp +++ b/tests/auto/qml/qqmlmoduleplugin/tst_qqmlmoduleplugin.cpp @@ -494,18 +494,22 @@ void tst_qqmlmoduleplugin::importsNested_data() { QTest::addColumn<QString>("file"); QTest::addColumn<QString>("errorFile"); - - // Note: no other test case should import the plugin used for this test, or the - // wrong order test will pass spuriously - QTest::newRow("wrongOrder") << "importsNested.1.qml" << "importsNested.1.errors.txt"; - QTest::newRow("missingImport") << "importsNested.3.qml" << "importsNested.3.errors.txt"; - QTest::newRow("invalidVersion") << "importsNested.4.qml" << "importsNested.4.errors.txt"; - QTest::newRow("correctOrder") << "importsNested.2.qml" << QString(); + QTest::addColumn<bool>("expectGreeting"); + + // NB: The order is wrong in the sense that it tries to load the "Nested" URI first, which + // is not visible in the file system. However, in the process of loading the non-nested + // URI it can discover the types for the nested one and insert them in the right place. + // This used to be an error but doesn't have to be. + QTest::newRow("wrongOrder") << "importsNested.1.qml" << QString() << true; + QTest::newRow("missingImport") << "importsNested.3.qml" << "importsNested.3.errors.txt" << false; + QTest::newRow("invalidVersion") << "importsNested.4.qml" << "importsNested.4.errors.txt" << false; + QTest::newRow("correctOrder") << "importsNested.2.qml" << QString() << false; } void tst_qqmlmoduleplugin::importsNested() { QFETCH(QString, file); QFETCH(QString, errorFile); + QFETCH(bool, expectGreeting); // Note: because imports are cached between test case data rows (and the plugins remain loaded), // these tests should really be run in new instances of the app... @@ -522,6 +526,10 @@ void tst_qqmlmoduleplugin::importsNested() QTest::ignoreMessage(QtWarningMsg, "Module 'org.qtproject.AutoTestQmlNestedPluginType' does not contain a module identifier directive - it cannot be protected from external registrations."); QQmlComponent component(&engine, testFile(file)); + + if (expectGreeting) + QTest::ignoreMessage(QtDebugMsg, "Hello"); + std::unique_ptr<QObject> obj { component.create() }; if (errorFile.isEmpty()) { diff --git a/tests/auto/qml/qqmltypeloader/data/qobjectSingletonUser.qml b/tests/auto/qml/qqmltypeloader/data/qobjectSingletonUser.qml new file mode 100644 index 0000000000..d8b380928c --- /dev/null +++ b/tests/auto/qml/qqmltypeloader/data/qobjectSingletonUser.qml @@ -0,0 +1,7 @@ +import QtQml +import Qt.example.qobjectSingleton 1.0 + +QtObject { + property int someValue: MyApi.someProperty + property int doneSomething: MyApi.doSomething() +} diff --git a/tests/auto/qml/qqmltypeloader/tst_qqmltypeloader.cpp b/tests/auto/qml/qqmltypeloader/tst_qqmltypeloader.cpp index 0f1ee2caf7..02435f7cdd 100644 --- a/tests/auto/qml/qqmltypeloader/tst_qqmltypeloader.cpp +++ b/tests/auto/qml/qqmltypeloader/tst_qqmltypeloader.cpp @@ -6,6 +6,7 @@ #include <QtCore/QRandomGenerator> #include <QtQml/qqmlengine.h> #include <QtQml/qqmlfile.h> +#include <QtQml/qqmlapplicationengine.h> #include <QtQml/qqmlnetworkaccessmanagerfactory.h> #include <QtQuick/qquickview.h> #include <QtQuick/qquickitem.h> @@ -55,6 +56,7 @@ private slots: void loadTypeOnShutdown(); void floodTypeLoaderEventQueue(); void retainQmlTypeAcrossEngines(); + void loadLocalTypesAfterRemoteFails(); private: void checkSingleton(const QString & dataDirectory); @@ -924,6 +926,65 @@ void tst_QQMLTypeLoader::retainQmlTypeAcrossEngines() QCOMPARE(mo1->superClass(), mo3->superClass()); } +class SingletonTypeExample : public QObject +{ + Q_OBJECT + Q_PROPERTY(int someProperty READ someProperty WRITE setSomeProperty NOTIFY somePropertyChanged) + +public: + explicit SingletonTypeExample(QObject* parent = nullptr) : QObject(parent) {} + + Q_INVOKABLE int doSomething() + { + setSomeProperty(5); + return m_someProperty; + } + + int someProperty() const { return m_someProperty; } + void setSomeProperty(int val) { + if (m_someProperty != val) { + m_someProperty = val; + emit somePropertyChanged(val); + } + } + +signals: + void somePropertyChanged(int newValue); + +private: + int m_someProperty = 0; +}; + +class HttpUrlInterceptor : public QQmlAbstractUrlInterceptor +{ +public: + QUrl intercept(const QUrl &path, DataType type) override + { + QUrl result = path; + if (path.scheme() == "http" && type == QmldirFile) + result.setFragment("qmldir"); + return result; + } +}; + +void tst_QQMLTypeLoader::loadLocalTypesAfterRemoteFails() +{ + std::unique_ptr<SingletonTypeExample> example = std::make_unique<SingletonTypeExample>(); + qmlRegisterSingletonInstance("Qt.example.qobjectSingleton", 1, 0, "MyApi", example.get()); + + HttpUrlInterceptor interceptor; + QQmlEngine engine; + engine.addUrlInterceptor(&interceptor); + engine.addImportPath(QString("http:/127.0.0.1/")); + + QQmlComponent component(&engine, testFileUrl("qobjectSingletonUser.qml")); + QTRY_VERIFY2(component.isReady(), qPrintable(component.errorString())); + + QScopedPointer<QObject> object(component.create()); + QCOMPARE(object->property("someValue").toInt(), 5); + QCOMPARE(object->property("doneSomething").toInt(), 5); +} + QTEST_MAIN(tst_QQMLTypeLoader) #include "tst_qqmltypeloader.moc" |