aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFabian Kosmale <fabian.kosmale@qt.io>2020-06-30 13:06:19 +0200
committerFabian Kosmale <fabian.kosmale@qt.io>2020-08-12 15:52:10 +0200
commitf358188cbd2e47f82d7de3612181e628e1f1c05c (patch)
tree8befff9d83c4e96bab0b2e9af7727238b2e9be87
parent26884a4f34cfe6bc10daa19f2096aa49ed0a31f2 (diff)
Do not set QQmlPropertyBinding until we reach finalize
If we install the binding eagerly, context properties cannot be resolved yet, as the context object has not been created so far. This causes issues with a QNotifiedProperty using a callback which accesses the current value, and thus forcing the binding evaluation while the object creation is still ongoing. Change-Id: I3bf3def04cd044371cb757a1854a3224a9c669b8 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
-rw-r--r--src/qml/qml/qqmlobjectcreator.cpp12
-rw-r--r--src/qml/qml/qqmlobjectcreator_p.h1
-rw-r--r--tests/auto/qml/qqmlecmascript/data/bindingOnQPropertyContextProperty.qml17
-rw-r--r--tests/auto/qml/qqmlecmascript/testtypes.cpp6
-rw-r--r--tests/auto/qml/qqmlecmascript/testtypes.h9
-rw-r--r--tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp18
6 files changed, 58 insertions, 5 deletions
diff --git a/src/qml/qml/qqmlobjectcreator.cpp b/src/qml/qml/qqmlobjectcreator.cpp
index 1018fbcb3b..1142618e2d 100644
--- a/src/qml/qml/qqmlobjectcreator.cpp
+++ b/src/qml/qml/qqmlobjectcreator.cpp
@@ -917,8 +917,7 @@ bool QQmlObjectCreator::setPropertyBinding(const QQmlPropertyData *bindingProper
QV4::Function *runtimeFunction = compilationUnit->runtimeFunctions[binding->value.compiledScriptIndex];
qmlBinding = QQmlPropertyBinding::create(bindingProperty, runtimeFunction, _scopeObject, context, currentQmlContext());
}
- void *argv[] = { &qmlBinding };
- _bindingTarget->qt_metacall(QMetaObject::SetQPropertyBinding, bindingProperty->coreIndex(), argv);
+ sharedState.data()->allQPropertyBindings.emplaceBack(_bindingTarget, bindingProperty->coreIndex(), qmlBinding);
} else {
// When writing bindings to grouped properties implemented as value types,
// such as point.x: { someExpression; }, then the binding is installed on
@@ -1419,6 +1418,15 @@ bool QQmlObjectCreator::finalize(QQmlInstantiationInterrupt &interrupt)
return false;
}
+ while (!sharedState->allQPropertyBindings.isEmpty()) {
+ auto& [target, index, qmlBinding] = sharedState->allQPropertyBindings.last();
+ void *argv[] = { &qmlBinding };
+ target->qt_metacall(QMetaObject::SetQPropertyBinding, index, argv);
+ sharedState->allQPropertyBindings.pop_back();
+ if (watcher.hasRecursed() || interrupt.shouldInterrupt())
+ return false;
+ }
+
if (QQmlVME::componentCompleteEnabled()) { // the qml designer does the component complete later
while (!sharedState->allParserStatusCallbacks.isEmpty()) {
QQmlObjectCompletionProfiler profiler(&sharedState->profiler);
diff --git a/src/qml/qml/qqmlobjectcreator_p.h b/src/qml/qml/qqmlobjectcreator_p.h
index 0b14ec6603..0059c6da7b 100644
--- a/src/qml/qml/qqmlobjectcreator_p.h
+++ b/src/qml/qml/qqmlobjectcreator_p.h
@@ -102,6 +102,7 @@ struct QQmlObjectCreatorSharedState : public QSharedData
QQmlVmeProfiler profiler;
QRecursionNode recursionNode;
RequiredProperties requiredProperties;
+ QList<std::tuple<QObject *, int, QUntypedPropertyBinding>> allQPropertyBindings;
bool hadRequiredProperties;
};
diff --git a/tests/auto/qml/qqmlecmascript/data/bindingOnQPropertyContextProperty.qml b/tests/auto/qml/qqmlecmascript/data/bindingOnQPropertyContextProperty.qml
new file mode 100644
index 0000000000..85d5f34e69
--- /dev/null
+++ b/tests/auto/qml/qqmlecmascript/data/bindingOnQPropertyContextProperty.qml
@@ -0,0 +1,17 @@
+import Qt.test 1.0
+import QtQuick 2
+
+Item {
+ id: root
+ property ClassWithQProperty2 testee: null
+ Repeater {
+ model: 2
+ Item {
+ ClassWithQProperty2 {
+ id: myself
+ value: index+1
+ Component.onCompleted: {if (index == 1) {root.testee = myself}}
+ }
+ }
+ }
+}
diff --git a/tests/auto/qml/qqmlecmascript/testtypes.cpp b/tests/auto/qml/qqmlecmascript/testtypes.cpp
index 2059650584..e7f2756ec3 100644
--- a/tests/auto/qml/qqmlecmascript/testtypes.cpp
+++ b/tests/auto/qml/qqmlecmascript/testtypes.cpp
@@ -456,6 +456,11 @@ void FloatingQObject::componentComplete()
Q_ASSERT(!parent());
}
+void ClassWithQProperty2::callback()
+{
+ Q_UNUSED(this->value.value()); // force evaluation
+}
+
void registerTypes()
{
qmlRegisterType<MyQmlObject>("Qt.test", 1,0, "MyQmlObjectAlias");
@@ -550,6 +555,7 @@ void registerTypes()
qmlRegisterType<ClashingNames>("Qt.test", 1, 0, "ClashingNames");
qmlRegisterType<ClassWithQProperty>("Qt.test", 1, 0, "ClassWithQProperty");
+ qmlRegisterType<ClassWithQProperty2>("Qt.test", 1, 0, "ClassWithQProperty2");
}
#include "testtypes.moc"
diff --git a/tests/auto/qml/qqmlecmascript/testtypes.h b/tests/auto/qml/qqmlecmascript/testtypes.h
index 514dfb6f8f..725624781b 100644
--- a/tests/auto/qml/qqmlecmascript/testtypes.h
+++ b/tests/auto/qml/qqmlecmascript/testtypes.h
@@ -1746,6 +1746,15 @@ public slots:
void selection(const QItemSelection &is, int n = 0) { funcCalled = QLatin1String("QItemSelection"); }
};
+struct ClassWithQProperty2 : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(float value)
+public:
+ void callback();
+ QNotifiedProperty<float, &ClassWithQProperty2::callback> value;
+};
+
void registerTypes();
#endif // TESTTYPES_H
diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp
index 52b2afe727..deb949e7a5 100644
--- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp
+++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp
@@ -383,6 +383,7 @@ private slots:
void semicolonAfterProperty();
void hugeStack();
void bindingOnQProperty();
+ void bindingOnQPropertyContextProperty();
void urlConstruction();
void urlPropertyInvalid();
void urlPropertySet();
@@ -9279,17 +9280,28 @@ void tst_qqmlecmascript::bindingOnQProperty()
QScopedPointer<QObject> test(component.create());
test->setProperty("externalValue", 42);
QCOMPARE(test->property("value").toInt(), 42);
- // Value hasn't changed yet...
- QCOMPARE(test->property("changeHandlerCount").toInt(), 0);
+ QCOMPARE(test->property("changeHandlerCount").toInt(), 1);
test->setProperty("externalValue", 100);
QCOMPARE(test->property("value").toInt(), 100);
- QCOMPARE(test->property("changeHandlerCount").toInt(), 1);
+ QCOMPARE(test->property("changeHandlerCount").toInt(), 2);
QVERIFY(qobject_cast<ClassWithQProperty*>(test.data()));
QProperty<float> &qprop = static_cast<ClassWithQProperty*>(test.data())->value;
QVERIFY(qprop.hasBinding());
}
+void tst_qqmlecmascript::bindingOnQPropertyContextProperty()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, testFileUrl("bindingOnQPropertyContextProperty.qml"));
+ QVERIFY2(component.isReady(), qPrintable(component.errorString()));
+ QScopedPointer<QObject> test(component.create());
+ QVERIFY(!test.isNull());
+ auto classWithQProperty = test->property("testee").value<ClassWithQProperty2 *>();
+ QVERIFY(classWithQProperty);
+ QCOMPARE(classWithQProperty->value.value(), 2);
+}
+
void tst_qqmlecmascript::urlConstruction()
{
QQmlEngine qmlengine;