aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlf Hermann <[email protected]>2025-04-08 15:34:55 +0200
committerUlf Hermann <[email protected]>2025-04-15 19:27:08 +0200
commit7d405c4e33d7d559cef3a22ee514e357732cd1b3 (patch)
treea679be4057cca89b372925cc066112ef9f9d8694
parent6a8478829747289cdcce2a6e9628b31cfd865f15 (diff)
QtQml: Load pre-registered types if no remote qmldir can be found
If we have a potentially remote qmldir we have to wait for it to surface before we load any pre-registered types. The qmldir might contain dependent imports. Once we've exhausted all possible remote qmldir URLs, we can load any pre-registered types. We have to unconditionally insert the import instance at the right place in the import namespace, though. Otherwise the imported types will get re-ordered. There are some pre-existing quirks to this behavior: We only trigger the code that postpones the loading if the QML engine has URL interceptors. Otherwise we load the pre-registered types right away. Changing this to do the right thing and first check the remote locations for any potential qmldirs would be a subtle change in behavior with large potential to break someone's code. We should not do it without a proper migration path. As a drive-by, fix the generation of warnings for unresolved imports. We only want to warn about actually unresolved imports. Previously, it warned about all imports it had to postpone if any of them was unresolved. Amends commit 46429839fedd79244559069bb4235a8b0e7ebf0a Pick-to: 6.9 6.8 6.5 Fixes: QTBUG-135334 Change-Id: Ib86ea58c19d96d2162f6735ce1caf88c562b65e7 Reviewed-by: Fabian Kosmale <[email protected]>
-rw-r--r--src/qml/qml/qqmltypedata.cpp35
-rw-r--r--src/qml/qml/qqmltypeloader.cpp124
-rw-r--r--src/qml/qml/qqmltypeloader_p.h3
-rw-r--r--tests/auto/qml/qqmlmoduleplugin/data/importsNested.1.errors.txt1
-rw-r--r--tests/auto/qml/qqmlmoduleplugin/data/importsNested.1.qml3
-rw-r--r--tests/auto/qml/qqmlmoduleplugin/nestedPlugin/nestedPlugin.cpp2
-rw-r--r--tests/auto/qml/qqmlmoduleplugin/tst_qqmlmoduleplugin.cpp22
-rw-r--r--tests/auto/qml/qqmltypeloader/data/qobjectSingletonUser.qml7
-rw-r--r--tests/auto/qml/qqmltypeloader/tst_qqmltypeloader.cpp61
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"