Skip to content

Commit

Permalink
Fix execution of deferred properties
Browse files Browse the repository at this point in the history
When deferred properties were assigned in multiple contexts, only the
outermost context was executed. Any deferred property assignments in
other inner contexts were never executed. Collect the deferred data to
a container to be able to execute them all.

Task-number: QTBUG-63200
Change-Id: I88fab27c1f81b5188430ada086dcc19842507e99
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
  • Loading branch information
J-P Nurmi committed Oct 18, 2017
1 parent 5a03ab8 commit 3b6eeee
Show file tree
Hide file tree
Showing 11 changed files with 162 additions and 31 deletions.
36 changes: 25 additions & 11 deletions src/qml/qml/qqmlcomponent.cpp
Expand Up @@ -878,19 +878,33 @@ QQmlComponentPrivate::beginCreate(QQmlContextData *context)
}

void QQmlComponentPrivate::beginDeferred(QQmlEnginePrivate *enginePriv,
QObject *object, ConstructionState *state)
QObject *object, DeferredState *deferredState)
{
enginePriv->inProgressCreations++;
state->errors.clear();
state->completePending = true;

QQmlData *ddata = QQmlData::get(object);
Q_ASSERT(ddata->deferredData);
QQmlData::DeferredData *deferredData = ddata->deferredData;
QQmlContextData *creationContext = 0;
state->creator.reset(new QQmlObjectCreator(deferredData->context->parent, deferredData->compilationUnit, creationContext));
if (!state->creator->populateDeferredProperties(object))
state->errors << state->creator->errors;
Q_ASSERT(!ddata->deferredData.isEmpty());

deferredState->constructionStates.reserve(ddata->deferredData.size());

for (QQmlData::DeferredData *deferredData : qAsConst(ddata->deferredData)) {
enginePriv->inProgressCreations++;

ConstructionState *state = new ConstructionState;
state->completePending = true;

QQmlContextData *creationContext = nullptr;
state->creator.reset(new QQmlObjectCreator(deferredData->context->parent, deferredData->compilationUnit, creationContext));

if (!state->creator->populateDeferredProperties(object, deferredData))
state->errors << state->creator->errors;

deferredState->constructionStates += state;
}
}

void QQmlComponentPrivate::completeDeferred(QQmlEnginePrivate *enginePriv, QQmlComponentPrivate::DeferredState *deferredState)
{
for (ConstructionState *state : qAsConst(deferredState->constructionStates))
complete(enginePriv, state);
}

void QQmlComponentPrivate::complete(QQmlEnginePrivate *enginePriv, ConstructionState *state)
Expand Down
13 changes: 11 additions & 2 deletions src/qml/qml/qqmlcomponent_p.h
Expand Up @@ -114,8 +114,17 @@ class Q_QML_PRIVATE_EXPORT QQmlComponentPrivate : public QObjectPrivate, public
};
ConstructionState state;

static void beginDeferred(QQmlEnginePrivate *enginePriv, QObject *object,
ConstructionState *state);
struct DeferredState {
~DeferredState() {
qDeleteAll(constructionStates);
constructionStates.clear();
}
QVector<ConstructionState *> constructionStates;
};

static void beginDeferred(QQmlEnginePrivate *enginePriv, QObject *object, DeferredState* deferredState);
static void completeDeferred(QQmlEnginePrivate *enginePriv, DeferredState *deferredState);

static void complete(QQmlEnginePrivate *enginePriv, ConstructionState *state);

QQmlEngine *engine;
Expand Down
5 changes: 4 additions & 1 deletion src/qml/qml/qqmldata_p.h
Expand Up @@ -57,6 +57,7 @@
#include <private/qv4value_p.h>
#include <private/qv4persistent_p.h>
#include <qjsengine.h>
#include <qvector.h>

QT_BEGIN_NAMESPACE

Expand Down Expand Up @@ -219,7 +220,9 @@ class Q_QML_PRIVATE_EXPORT QQmlData : public QAbstractDeclarativeData
QQmlContextData *context;//Could be either context or outerContext
};
QV4::CompiledData::CompilationUnit *compilationUnit;
DeferredData *deferredData;
QVector<DeferredData *> deferredData;

void releaseDeferredData();

QV4::WeakValue jsWrapper;

Expand Down
27 changes: 15 additions & 12 deletions src/qml/qml/qqmlengine.cpp
Expand Up @@ -739,7 +739,7 @@ QQmlData::QQmlData()
hasInterceptorMetaObject(false), hasVMEMetaObject(false), parentFrozen(false),
bindingBitsSize(MaxInlineBits), bindingBitsValue(0), notifyList(0),
bindings(0), signalHandlers(0), nextContextObject(0), prevContextObject(0),
lineNumber(0), columnNumber(0), jsEngineId(0), compilationUnit(0), deferredData(0),
lineNumber(0), columnNumber(0), jsEngineId(0), compilationUnit(0),
propertyCache(0), guards(0), extendedData(0)
{
init();
Expand Down Expand Up @@ -1469,18 +1469,16 @@ void qmlExecuteDeferred(QObject *object)
{
QQmlData *data = QQmlData::get(object);

if (data && data->deferredData && !data->wasDeleted(object)) {
if (data && !data->deferredData.isEmpty() && !data->wasDeleted(object)) {
QQmlEnginePrivate *ep = QQmlEnginePrivate::get(data->context->engine);

QQmlComponentPrivate::ConstructionState state;
QQmlComponentPrivate::DeferredState state;
QQmlComponentPrivate::beginDeferred(ep, object, &state);

// Release the reference for the deferral action (we still have one from construction)
data->deferredData->compilationUnit->release();
delete data->deferredData;
data->deferredData = 0;
data->releaseDeferredData();

QQmlComponentPrivate::complete(ep, &state);
QQmlComponentPrivate::completeDeferred(ep, &state);
}
}

Expand Down Expand Up @@ -1638,6 +1636,15 @@ void QQmlData::NotifyList::layout()
todo = 0;
}

void QQmlData::releaseDeferredData()
{
for (DeferredData *deferData : qAsConst(deferredData)) {
deferData->compilationUnit->release();
delete deferData;
}
deferredData.clear();
}

void QQmlData::addNotify(int index, QQmlNotifierEndpoint *endpoint)
{
if (!notifyList) {
Expand Down Expand Up @@ -1712,11 +1719,7 @@ void QQmlData::destroyed(QObject *object)
compilationUnit = 0;
}

if (deferredData) {
deferredData->compilationUnit->release();
delete deferredData;
deferredData = 0;
}
releaseDeferredData();

QQmlBoundSignal *signalHandler = signalHandlers;
while (signalHandler) {
Expand Down
8 changes: 4 additions & 4 deletions src/qml/qml/qqmlobjectcreator.cpp
Expand Up @@ -233,10 +233,10 @@ QObject *QQmlObjectCreator::create(int subComponentIndex, QObject *parent, QQmlI
return instance;
}

bool QQmlObjectCreator::populateDeferredProperties(QObject *instance)
bool QQmlObjectCreator::populateDeferredProperties(QObject *instance, QQmlData::DeferredData *deferredData)
{
QQmlData *declarativeData = QQmlData::get(instance);
context = declarativeData->deferredData->context;
context = deferredData->context;
sharedState->rootContext = context;

QObject *bindingTarget = instance;
Expand All @@ -260,7 +260,7 @@ bool QQmlObjectCreator::populateDeferredProperties(QObject *instance)
qSwap(_propertyCache, cache);
qSwap(_qobject, instance);

int objectIndex = declarativeData->deferredData->deferredIdx;
int objectIndex = deferredData->deferredIdx;
qSwap(_compiledObjectIndex, objectIndex);

const QV4::CompiledData::Object *obj = qmlUnit->objectAt(_compiledObjectIndex);
Expand Down Expand Up @@ -1347,7 +1347,7 @@ bool QQmlObjectCreator::populateInstance(int index, QObject *instance, QObject *
deferData->compilationUnit = compilationUnit;
deferData->compilationUnit->addref();
deferData->context = context;
_ddata->deferredData = deferData;
_ddata->deferredData.append(deferData);
}

if (_compiledObject->nFunctions > 0)
Expand Down
2 changes: 1 addition & 1 deletion src/qml/qml/qqmlobjectcreator_p.h
Expand Up @@ -89,7 +89,7 @@ class QQmlObjectCreator
~QQmlObjectCreator();

QObject *create(int subComponentIndex = -1, QObject *parent = 0, QQmlInstantiationInterrupt *interrupt = 0);
bool populateDeferredProperties(QObject *instance);
bool populateDeferredProperties(QObject *instance, QQmlData::DeferredData *deferredData);
QQmlContextData *finalize(QQmlInstantiationInterrupt &interrupt);
void cancel(QObject *object);
void clear();
Expand Down
19 changes: 19 additions & 0 deletions tests/auto/qml/qqmllanguage/data/MyDeferredProperties.qml
@@ -0,0 +1,19 @@
import QtQml 2.0
import Test 1.0
DeferredProperties {
groupProperty: QtObject {
objectName: "innerobj"
property bool wasCompleted: false
Component.onCompleted: wasCompleted = true
}
QtObject {
objectName: "innerlist1"
property bool wasCompleted: false
Component.onCompleted: wasCompleted = true
}
QtObject {
objectName: "innerlist2"
property bool wasCompleted: false
Component.onCompleted: wasCompleted = true
}
}
19 changes: 19 additions & 0 deletions tests/auto/qml/qqmllanguage/data/deferredProperties.qml
@@ -0,0 +1,19 @@
import QtQml 2.0
import Test 1.0
MyDeferredProperties {
groupProperty: QtObject {
objectName: "outerobj"
property bool wasCompleted: false
Component.onCompleted: wasCompleted = true
}
QtObject {
objectName: "outerlist1"
property bool wasCompleted: false
Component.onCompleted: wasCompleted = true
}
QtObject {
objectName: "outerlist2"
property bool wasCompleted: false
Component.onCompleted: wasCompleted = true
}
}
1 change: 1 addition & 0 deletions tests/auto/qml/qqmllanguage/testtypes.cpp
Expand Up @@ -105,6 +105,7 @@ void registerTypes()
qmlRegisterType<MyArrayBufferTestClass>("Test", 1, 0, "MyArrayBufferTestClass");

qmlRegisterType<LazyDeferredSubObject>("Test", 1, 0, "LazyDeferredSubObject");
qmlRegisterType<DeferredProperties>("Test", 1, 0, "DeferredProperties");
}

QVariant myCustomVariantTypeConverter(const QString &data)
Expand Down
15 changes: 15 additions & 0 deletions tests/auto/qml/qqmllanguage/testtypes.h
Expand Up @@ -1351,6 +1351,21 @@ class LazyDeferredSubObject : public QObject
QObject *obj;
};

class DeferredProperties : public QObject
{
Q_OBJECT
Q_PROPERTY(QObject *groupProperty MEMBER m_group)
Q_PROPERTY(QQmlListProperty<QObject> listProperty READ listProperty)
Q_CLASSINFO("DeferredPropertyNames", "groupProperty,listProperty")
Q_CLASSINFO("DefaultProperty", "listProperty")
public:
QQmlListProperty<QObject> listProperty() { return QQmlListProperty<QObject>(this, m_list); }

private:
QObject *m_group = 0;
QObjectList m_list;
};

void registerTypes();

#endif // TESTTYPES_H
48 changes: 48 additions & 0 deletions tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
Expand Up @@ -248,6 +248,7 @@ private slots:

void rootObjectInCreationNotForSubObjects();
void lazyDeferredSubObject();
void deferredProperties();

void noChildEvents();

Expand Down Expand Up @@ -4258,6 +4259,53 @@ void tst_qqmllanguage::lazyDeferredSubObject()
QCOMPARE(subObject->objectName(), QStringLiteral("custom"));
}

// QTBUG-63200
void tst_qqmllanguage::deferredProperties()
{
QQmlComponent component(&engine, testFile("deferredProperties.qml"));
VERIFY_ERRORS(0);
QScopedPointer<QObject> object(component.create());
QVERIFY(!object.isNull());

QObject *innerObj = object->findChild<QObject *>(QStringLiteral("innerobj"));
QVERIFY(!innerObj);

QObject *outerObj = object->findChild<QObject *>(QStringLiteral("outerobj"));
QVERIFY(!outerObj);

QObject *groupProperty = object->property("groupProperty").value<QObject *>();
QVERIFY(!groupProperty);

QQmlListProperty<QObject> listProperty = object->property("listProperty").value<QQmlListProperty<QObject>>();
QCOMPARE(listProperty.count(&listProperty), 0);

qmlExecuteDeferred(object.data());

innerObj = object->findChild<QObject *>(QStringLiteral("innerobj")); // MyDeferredListProperty.qml
QVERIFY(innerObj);
QCOMPARE(innerObj->property("wasCompleted"), QVariant(true));

outerObj = object->findChild<QObject *>(QStringLiteral("outerobj")); // deferredListProperty.qml
QVERIFY(outerObj);
QCOMPARE(outerObj->property("wasCompleted"), QVariant(true));

groupProperty = object->property("groupProperty").value<QObject *>();
QCOMPARE(groupProperty, outerObj);

listProperty = object->property("listProperty").value<QQmlListProperty<QObject>>();
QCOMPARE(listProperty.count(&listProperty), 4);

QCOMPARE(listProperty.at(&listProperty, 0)->objectName(), QStringLiteral("innerlist1")); // MyDeferredListProperty.qml
QCOMPARE(listProperty.at(&listProperty, 0)->property("wasCompleted"), QVariant(true));
QCOMPARE(listProperty.at(&listProperty, 1)->objectName(), QStringLiteral("innerlist2")); // MyDeferredListProperty.qml
QCOMPARE(listProperty.at(&listProperty, 1)->property("wasCompleted"), QVariant(true));

QCOMPARE(listProperty.at(&listProperty, 2)->objectName(), QStringLiteral("outerlist1")); // deferredListProperty.qml
QCOMPARE(listProperty.at(&listProperty, 2)->property("wasCompleted"), QVariant(true));
QCOMPARE(listProperty.at(&listProperty, 3)->objectName(), QStringLiteral("outerlist2")); // deferredListProperty.qml
QCOMPARE(listProperty.at(&listProperty, 3)->property("wasCompleted"), QVariant(true));
}

void tst_qqmllanguage::noChildEvents()
{
QQmlComponent component(&engine);
Expand Down

0 comments on commit 3b6eeee

Please sign in to comment.