aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/qml/qml/qqmldata_p.h3
-rw-r--r--src/qml/qml/qqmlengine.cpp35
-rw-r--r--src/qml/qml/qqmlobjectcreator.cpp85
-rw-r--r--src/qml/qml/qqmlobjectcreator_p.h3
-rw-r--r--tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp152
5 files changed, 265 insertions, 13 deletions
diff --git a/src/qml/qml/qqmldata_p.h b/src/qml/qml/qqmldata_p.h
index d692feb975..63994a392d 100644
--- a/src/qml/qml/qqmldata_p.h
+++ b/src/qml/qml/qqmldata_p.h
@@ -76,6 +76,7 @@ class QQmlNotifierEndpoint;
namespace QV4 {
namespace CompiledData {
struct CompilationUnit;
+struct Binding;
}
}
@@ -216,12 +217,14 @@ public:
struct DeferredData {
unsigned int deferredIdx;
+ QMultiHash<int, const QV4::CompiledData::Binding *> bindings;
QV4::CompiledData::CompilationUnit *compilationUnit;//Not always the same as the other compilation unit
QQmlContextData *context;//Could be either context or outerContext
};
QV4::CompiledData::CompilationUnit *compilationUnit;
QVector<DeferredData *> deferredData;
+ void deferData(int objectIndex, QV4::CompiledData::CompilationUnit *, QQmlContextData *);
void releaseDeferredData();
QV4::WeakValue jsWrapper;
diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp
index d99bec4c52..612c3439c1 100644
--- a/src/qml/qml/qqmlengine.cpp
+++ b/src/qml/qml/qqmlengine.cpp
@@ -1639,13 +1639,40 @@ void QQmlData::NotifyList::layout()
todo = 0;
}
+void QQmlData::deferData(int objectIndex, QV4::CompiledData::CompilationUnit *compilationUnit, QQmlContextData *context)
+{
+ QQmlData::DeferredData *deferData = new QQmlData::DeferredData;
+ deferData->deferredIdx = objectIndex;
+ deferData->compilationUnit = compilationUnit;
+ deferData->compilationUnit->addref();
+ deferData->context = context;
+
+ const QV4::CompiledData::Object *compiledObject = compilationUnit->objectAt(objectIndex);
+ const QV4::CompiledData::BindingPropertyData &propertyData = compilationUnit->bindingPropertyDataPerObject.at(objectIndex);
+
+ const QV4::CompiledData::Binding *binding = compiledObject->bindingTable();
+ for (quint32 i = 0; i < compiledObject->nBindings; ++i, ++binding) {
+ const QQmlPropertyData *property = propertyData.at(i);
+ if (property && binding->flags & QV4::CompiledData::Binding::IsDeferredBinding)
+ deferData->bindings.insert(property->coreIndex(), binding);
+ }
+
+ deferredData.append(deferData);
+}
+
void QQmlData::releaseDeferredData()
{
- for (DeferredData *deferData : qAsConst(deferredData)) {
- deferData->compilationUnit->release();
- delete deferData;
+ auto it = deferredData.begin();
+ while (it != deferredData.end()) {
+ DeferredData *deferData = *it;
+ if (deferData->bindings.isEmpty()) {
+ deferData->compilationUnit->release();
+ delete deferData;
+ it = deferredData.erase(it);
+ } else {
+ ++it;
+ }
}
- deferredData.clear();
}
void QQmlData::addNotify(int index, QQmlNotifierEndpoint *endpoint)
diff --git a/src/qml/qml/qqmlobjectcreator.cpp b/src/qml/qml/qqmlobjectcreator.cpp
index b2f1421bcb..3663b06d55 100644
--- a/src/qml/qml/qqmlobjectcreator.cpp
+++ b/src/qml/qml/qqmlobjectcreator.cpp
@@ -233,6 +233,7 @@ QObject *QQmlObjectCreator::create(int subComponentIndex, QObject *parent, QQmlI
return instance;
}
+// ### unify or keep in sync with populateDeferredBinding()
bool QQmlObjectCreator::populateDeferredProperties(QObject *instance, QQmlData::DeferredData *deferredData)
{
QQmlData *declarativeData = QQmlData::get(instance);
@@ -283,6 +284,80 @@ bool QQmlObjectCreator::populateDeferredProperties(QObject *instance, QQmlData::
qSwap(_qmlContext, qmlContext);
qSwap(_scopeObject, scopeObject);
+ deferredData->bindings.clear();
+ phase = ObjectsCreated;
+
+ return errors.isEmpty();
+}
+
+// ### unify or keep in sync with populateDeferredProperties()
+bool QQmlObjectCreator::populateDeferredBinding(const QQmlProperty &qmlProperty, QQmlData::DeferredData *deferredData, const QV4::CompiledData::Binding *binding)
+{
+ Q_ASSERT(binding->flags & QV4::CompiledData::Binding::IsDeferredBinding);
+
+ QObject *instance = qmlProperty.object();
+ QQmlData *declarativeData = QQmlData::get(instance);
+ context = deferredData->context;
+ sharedState->rootContext = context;
+
+ QObject *bindingTarget = instance;
+
+ QQmlRefPointer<QQmlPropertyCache> cache = declarativeData->propertyCache;
+ QQmlVMEMetaObject *vmeMetaObject = QQmlVMEMetaObject::get(instance);
+
+ QObject *scopeObject = instance;
+ qSwap(_scopeObject, scopeObject);
+
+ QV4::Scope valueScope(v4);
+
+ Q_ASSERT(topLevelCreator);
+ if (!sharedState->allJavaScriptObjects)
+ sharedState->allJavaScriptObjects = valueScope.alloc(compilationUnit->totalObjectCount);
+
+ QV4::QmlContext *qmlContext = static_cast<QV4::QmlContext *>(valueScope.alloc(1));
+
+ qSwap(_qmlContext, qmlContext);
+
+ qSwap(_propertyCache, cache);
+ qSwap(_qobject, instance);
+
+ int objectIndex = deferredData->deferredIdx;
+ qSwap(_compiledObjectIndex, objectIndex);
+
+ const QV4::CompiledData::Object *obj = qmlUnit->objectAt(_compiledObjectIndex);
+ qSwap(_compiledObject, obj);
+
+ qSwap(_ddata, declarativeData);
+ qSwap(_bindingTarget, bindingTarget);
+ qSwap(_vmeMetaObject, vmeMetaObject);
+
+ QQmlListProperty<void> savedList;
+ qSwap(_currentList, savedList);
+
+ const QQmlPropertyData &property = QQmlPropertyPrivate::get(qmlProperty)->core;
+
+ if (property.isQList()) {
+ void *argv[1] = { (void*)&_currentList };
+ QMetaObject::metacall(_qobject, QMetaObject::ReadProperty, property.coreIndex(), argv);
+ } else if (_currentList.object) {
+ _currentList = QQmlListProperty<void>();
+ }
+
+ setPropertyBinding(&property, binding);
+
+ qSwap(_currentList, savedList);
+
+ qSwap(_vmeMetaObject, vmeMetaObject);
+ qSwap(_bindingTarget, bindingTarget);
+ qSwap(_ddata, declarativeData);
+ qSwap(_compiledObject, obj);
+ qSwap(_compiledObjectIndex, objectIndex);
+ qSwap(_qobject, instance);
+ qSwap(_propertyCache, cache);
+
+ qSwap(_qmlContext, qmlContext);
+ qSwap(_scopeObject, scopeObject);
+
phase = ObjectsCreated;
return errors.isEmpty();
@@ -1341,14 +1416,8 @@ bool QQmlObjectCreator::populateInstance(int index, QObject *instance, QObject *
qSwap(_propertyCache, cache);
qSwap(_vmeMetaObject, vmeMetaObject);
- if (_compiledObject->flags & QV4::CompiledData::Object::HasDeferredBindings) {
- QQmlData::DeferredData *deferData = new QQmlData::DeferredData;
- deferData->deferredIdx = _compiledObjectIndex;
- deferData->compilationUnit = compilationUnit;
- deferData->compilationUnit->addref();
- deferData->context = context;
- _ddata->deferredData.append(deferData);
- }
+ if (_compiledObject->flags & QV4::CompiledData::Object::HasDeferredBindings)
+ _ddata->deferData(_compiledObjectIndex, compilationUnit, context);
if (_compiledObject->nFunctions > 0)
setupFunctions();
diff --git a/src/qml/qml/qqmlobjectcreator_p.h b/src/qml/qml/qqmlobjectcreator_p.h
index 0c2d427c58..e2371cb4f1 100644
--- a/src/qml/qml/qqmlobjectcreator_p.h
+++ b/src/qml/qml/qqmlobjectcreator_p.h
@@ -81,7 +81,7 @@ struct QQmlObjectCreatorSharedState : public QSharedData
QRecursionNode recursionNode;
};
-class QQmlObjectCreator
+class Q_QML_PRIVATE_EXPORT QQmlObjectCreator
{
Q_DECLARE_TR_FUNCTIONS(QQmlObjectCreator)
public:
@@ -90,6 +90,7 @@ public:
QObject *create(int subComponentIndex = -1, QObject *parent = 0, QQmlInstantiationInterrupt *interrupt = 0);
bool populateDeferredProperties(QObject *instance, QQmlData::DeferredData *deferredData);
+ bool populateDeferredBinding(const QQmlProperty &qmlProperty, QQmlData::DeferredData *deferredData, const QV4::CompiledData::Binding *binding);
QQmlContextData *finalize(QQmlInstantiationInterrupt &interrupt);
void cancel(QObject *object);
void clear();
diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
index f4d31d9e60..d04a83cd5b 100644
--- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
+++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
@@ -44,6 +44,7 @@
#include <private/qqmlglobal_p.h>
#include <private/qqmlscriptstring_p.h>
#include <private/qqmlvmemetaobject_p.h>
+#include <private/qqmlcomponent_p.h>
#include "testtypes.h"
#include "testhttpserver.h"
@@ -249,6 +250,7 @@ private slots:
void rootObjectInCreationNotForSubObjects();
void lazyDeferredSubObject();
void deferredProperties();
+ void executeDeferredPropertiesOnce();
void noChildEvents();
@@ -4279,8 +4281,17 @@ void tst_qqmllanguage::deferredProperties()
QQmlListProperty<QObject> listProperty = object->property("listProperty").value<QQmlListProperty<QObject>>();
QCOMPARE(listProperty.count(&listProperty), 0);
+ QQmlData *qmlData = QQmlData::get(object.data());
+ QVERIFY(qmlData);
+
+ QCOMPARE(qmlData->deferredData.count(), 2); // MyDeferredListProperty.qml + deferredListProperty.qml
+ QCOMPARE(qmlData->deferredData.first()->bindings.count(), 3); // "innerobj", "innerlist1", "innerlist2"
+ QCOMPARE(qmlData->deferredData.last()->bindings.count(), 3); // "outerobj", "outerlist1", "outerlist2"
+
qmlExecuteDeferred(object.data());
+ QCOMPARE(qmlData->deferredData.count(), 0);
+
innerObj = object->findChild<QObject *>(QStringLiteral("innerobj")); // MyDeferredListProperty.qml
QVERIFY(innerObj);
QCOMPARE(innerObj->property("wasCompleted"), QVariant(true));
@@ -4306,6 +4317,147 @@ void tst_qqmllanguage::deferredProperties()
QCOMPARE(listProperty.at(&listProperty, 3)->property("wasCompleted"), QVariant(true));
}
+static void beginDeferredOnce(QQmlEnginePrivate *enginePriv,
+ const QQmlProperty &property, QQmlComponentPrivate::DeferredState *deferredState)
+{
+ QObject *object = property.object();
+ QQmlData *ddata = QQmlData::get(object);
+ Q_ASSERT(!ddata->deferredData.isEmpty());
+
+ int propertyIndex = property.index();
+
+ for (auto dit = ddata->deferredData.rbegin(); dit != ddata->deferredData.rend(); ++dit) {
+ QQmlData::DeferredData *deferData = *dit;
+
+ auto range = deferData->bindings.equal_range(propertyIndex);
+ if (range.first == deferData->bindings.end())
+ continue;
+
+ QQmlComponentPrivate::ConstructionState *state = new QQmlComponentPrivate::ConstructionState;
+ state->completePending = true;
+
+ QQmlContextData *creationContext = nullptr;
+ state->creator.reset(new QQmlObjectCreator(deferData->context->parent, deferData->compilationUnit, creationContext));
+
+ enginePriv->inProgressCreations++;
+
+ typedef QMultiHash<int, const QV4::CompiledData::Binding *> QV4PropertyBindingHash;
+ auto it = std::reverse_iterator<QV4PropertyBindingHash::iterator>(range.second);
+ auto last = std::reverse_iterator<QV4PropertyBindingHash::iterator>(range.first);
+ while (it != last) {
+ if (!state->creator->populateDeferredBinding(property, deferData, *it))
+ state->errors << state->creator->errors;
+ ++it;
+ }
+
+ deferredState->constructionStates += state;
+
+ // Cleanup any remaining deferred bindings for this property, also in inner contexts,
+ // to avoid executing them later and overriding the property that was just populated.
+ while (dit != ddata->deferredData.rend()) {
+ (*dit)->bindings.remove(propertyIndex);
+ ++dit;
+ }
+ break;
+ }
+}
+
+static void testExecuteDeferredOnce(const QQmlProperty &property)
+{
+ QObject *object = property.object();
+ QQmlData *data = QQmlData::get(object);
+ if (data && !data->deferredData.isEmpty() && !data->wasDeleted(object)) {
+ QQmlEnginePrivate *ep = QQmlEnginePrivate::get(data->context->engine);
+
+ QQmlComponentPrivate::DeferredState state;
+ beginDeferredOnce(ep, property, &state);
+
+ // Release deferred data for those compilation units that no longer have deferred bindings
+ data->releaseDeferredData();
+
+ QQmlComponentPrivate::completeDeferred(ep, &state);
+ }
+}
+
+void tst_qqmllanguage::executeDeferredPropertiesOnce()
+{
+ QQmlComponent component(&engine, testFile("deferredProperties.qml"));
+ VERIFY_ERRORS(0);
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+
+ QObjectList innerObjsAtCreation = object->findChildren<QObject *>(QStringLiteral("innerobj"));
+ QVERIFY(innerObjsAtCreation.isEmpty());
+
+ QObjectList outerObjsAtCreation = object->findChildren<QObject *>(QStringLiteral("outerobj"));
+ QVERIFY(outerObjsAtCreation.isEmpty());
+
+ QObject *groupProperty = object->property("groupProperty").value<QObject *>();
+ QVERIFY(!groupProperty);
+
+ QQmlListProperty<QObject> listProperty = object->property("listProperty").value<QQmlListProperty<QObject>>();
+ QCOMPARE(listProperty.count(&listProperty), 0);
+
+ QQmlData *qmlData = QQmlData::get(object.data());
+ QVERIFY(qmlData);
+
+ QCOMPARE(qmlData->deferredData.count(), 2); // MyDeferredListProperty.qml + deferredListProperty.qml
+ QCOMPARE(qmlData->deferredData.first()->bindings.count(), 3); // "innerobj", "innerlist1", "innerlist2"
+ QCOMPARE(qmlData->deferredData.last()->bindings.count(), 3); // "outerobj", "outerlist1", "outerlist2"
+
+ // first execution creates the outer object
+ testExecuteDeferredOnce(QQmlProperty(object.data(), "groupProperty"));
+
+ QCOMPARE(qmlData->deferredData.count(), 2); // MyDeferredListProperty.qml + deferredListProperty.qml
+ QCOMPARE(qmlData->deferredData.first()->bindings.count(), 2); // "innerlist1", "innerlist2"
+ QCOMPARE(qmlData->deferredData.last()->bindings.count(), 2); // "outerlist1", "outerlist2"
+
+ QObjectList innerObjsAfterFirstExecute = object->findChildren<QObject *>(QStringLiteral("innerobj")); // MyDeferredListProperty.qml
+ QVERIFY(innerObjsAfterFirstExecute.isEmpty());
+
+ QObjectList outerObjsAfterFirstExecute = object->findChildren<QObject *>(QStringLiteral("outerobj")); // deferredListProperty.qml
+ QCOMPARE(outerObjsAfterFirstExecute.count(), 1);
+ QCOMPARE(outerObjsAfterFirstExecute.first()->property("wasCompleted"), QVariant(true));
+
+ groupProperty = object->property("groupProperty").value<QObject *>();
+ QCOMPARE(groupProperty, outerObjsAfterFirstExecute.first());
+
+ listProperty = object->property("listProperty").value<QQmlListProperty<QObject>>();
+ QCOMPARE(listProperty.count(&listProperty), 0);
+
+ // re-execution does nothing (to avoid overriding the property)
+ testExecuteDeferredOnce(QQmlProperty(object.data(), "groupProperty"));
+
+ QCOMPARE(qmlData->deferredData.count(), 2); // MyDeferredListProperty.qml + deferredListProperty.qml
+ QCOMPARE(qmlData->deferredData.first()->bindings.count(), 2); // "innerlist1", "innerlist2"
+ QCOMPARE(qmlData->deferredData.last()->bindings.count(), 2); // "outerlist1", "outerlist2"
+
+ QObjectList innerObjsAfterSecondExecute = object->findChildren<QObject *>(QStringLiteral("innerobj")); // MyDeferredListProperty.qml
+ QVERIFY(innerObjsAfterSecondExecute.isEmpty());
+
+ QObjectList outerObjsAfterSecondExecute = object->findChildren<QObject *>(QStringLiteral("outerobj")); // deferredListProperty.qml
+ QCOMPARE(outerObjsAfterFirstExecute, outerObjsAfterSecondExecute);
+
+ groupProperty = object->property("groupProperty").value<QObject *>();
+ QCOMPARE(groupProperty, outerObjsAfterFirstExecute.first());
+
+ listProperty = object->property("listProperty").value<QQmlListProperty<QObject>>();
+ QCOMPARE(listProperty.count(&listProperty), 0);
+
+ // execution of a list property should execute all outer list bindings
+ testExecuteDeferredOnce(QQmlProperty(object.data(), "listProperty"));
+
+ QCOMPARE(qmlData->deferredData.count(), 0);
+
+ listProperty = object->property("listProperty").value<QQmlListProperty<QObject>>();
+ QCOMPARE(listProperty.count(&listProperty), 2);
+
+ QCOMPARE(listProperty.at(&listProperty, 0)->objectName(), QStringLiteral("outerlist1")); // deferredListProperty.qml
+ QCOMPARE(listProperty.at(&listProperty, 0)->property("wasCompleted"), QVariant(true));
+ QCOMPARE(listProperty.at(&listProperty, 1)->objectName(), QStringLiteral("outerlist2")); // deferredListProperty.qml
+ QCOMPARE(listProperty.at(&listProperty, 1)->property("wasCompleted"), QVariant(true));
+}
+
void tst_qqmllanguage::noChildEvents()
{
QQmlComponent component(&engine);