1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
|
Description: QQuickItem: avoid emitting signals during destruction
If a QQuickItem is in the QQuickItem destructor, then it is both unsafe
and unnecessary to emit property change notifications. Connected code
can no longer rely on the state of the emitting object - if it was
originally a subclass of QQuickItem, then those subclass destructors
will already have run. And the QQuickItem destructor will also have
partially run, leaving the object in an undefined state.
.
Add a flag that we set to true at the top of ~QQuickItem, and don't emit
visibleChildrenChanged, parentChanged, visibleChanged, and
childrenChanged for items that are partially destroyed already.
.
[ChangeLog][Qt Quick][QQuickItem] QQuickItem no longer emits change
notifications for the parent, children, visible, and visibleChildren
properties while it is being destroyed.
Origin: upstream, https://code.qt.io/cgit/qt/qtdeclarative.git/commit/?id=74873324bdf33997
Last-Update: 2023-02-26
--- a/src/quick/items/qquickitem.cpp
+++ b/src/quick/items/qquickitem.cpp
@@ -2328,6 +2328,7 @@ QQuickItem::QQuickItem(QQuickItemPrivate
QQuickItem::~QQuickItem()
{
Q_D(QQuickItem);
+ d->inDestructor = true;
if (d->windowRefCount > 1)
d->windowRefCount = 1; // Make sure window is set to null in next call to derefWindow().
@@ -2695,9 +2696,8 @@ void QQuickItem::setParentItem(QQuickIte
const bool wasVisible = isVisible();
op->removeChild(this);
- if (wasVisible) {
+ if (wasVisible && !op->inDestructor)
emit oldParentItem->visibleChildrenChanged();
- }
} else if (d->window) {
QQuickWindowPrivate::get(d->window)->parentlessItems.remove(this);
}
@@ -2774,8 +2774,9 @@ void QQuickItem::setParentItem(QQuickIte
d->itemChange(ItemParentHasChanged, d->parentItem);
- emit parentChanged(d->parentItem);
- if (isVisible() && d->parentItem)
+ if (!d->inDestructor)
+ emit parentChanged(d->parentItem);
+ if (isVisible() && d->parentItem && !QQuickItemPrivate::get(d->parentItem)->inDestructor)
emit d->parentItem->visibleChildrenChanged();
}
@@ -2971,7 +2972,8 @@ void QQuickItemPrivate::removeChild(QQui
itemChange(QQuickItem::ItemChildRemovedChange, child);
- emit q->childrenChanged();
+ if (!inDestructor)
+ emit q->childrenChanged();
}
void QQuickItemPrivate::refWindow(QQuickWindow *c)
@@ -3200,6 +3202,7 @@ QQuickItemPrivate::QQuickItemPrivate()
, touchEnabled(false)
#endif
, hasCursorHandler(false)
+ , inDestructor(false)
, dirtyAttributes(0)
, nextDirtyItem(nullptr)
, prevDirtyItem(nullptr)
@@ -6112,9 +6115,11 @@ bool QQuickItemPrivate::setEffectiveVisi
QAccessible::updateAccessibility(&ev);
}
#endif
- emit q->visibleChanged();
- if (childVisibilityChanged)
- emit q->visibleChildrenChanged();
+ if (!inDestructor) {
+ emit q->visibleChanged();
+ if (childVisibilityChanged)
+ emit q->visibleChildrenChanged();
+ }
return true; // effective visibility DID change
}
--- a/src/quick/items/qquickitem_p.h
+++ b/src/quick/items/qquickitem_p.h
@@ -472,6 +472,7 @@ public:
bool replayingPressEvent:1;
bool touchEnabled:1;
bool hasCursorHandler:1;
+ quint32 inDestructor:1; // has entered ~QQuickItem
enum DirtyType {
TransformOrigin = 0x00000001,
--- a/tests/auto/quick/qquickitem2/tst_qquickitem.cpp
+++ b/tests/auto/quick/qquickitem2/tst_qquickitem.cpp
@@ -132,6 +132,8 @@ private slots:
void grab();
+ void signalsOnDestruction();
+
private:
QQmlEngine engine;
bool qt_tab_all_widgets() {
@@ -3532,6 +3534,52 @@ void tst_QQuickItem::isAncestorOf()
QVERIFY(!sub2.isAncestorOf(&sub2));
}
+/*
+ Items that are getting destroyed should not emit property change notifications.
+*/
+void tst_QQuickItem::signalsOnDestruction()
+{
+ QQuickWindow window;
+ window.show();
+
+ // visual children, but not QObject children
+ std::unique_ptr<QQuickItem> parent(new QQuickItem(window.contentItem()));
+ std::unique_ptr<QQuickItem> child(new QQuickItem);
+ std::unique_ptr<QQuickItem> grandChild(new QQuickItem);
+
+ QSignalSpy childrenSpy(parent.get(), &QQuickItem::childrenChanged);
+ QSignalSpy visibleChildrenSpy(parent.get(), &QQuickItem::visibleChildrenChanged);
+ QSignalSpy childParentSpy(child.get(), &QQuickItem::parentChanged);
+ QSignalSpy childChildrenSpy(child.get(), &QQuickItem::childrenChanged);
+ QSignalSpy childVisibleChildrenSpy(child.get(), &QQuickItem::visibleChanged);
+ QSignalSpy grandChildParentSpy(grandChild.get(), &QQuickItem::parentChanged);
+
+ child->setParentItem(parent.get());
+ QCOMPARE(childrenSpy.count(), 1);
+ QCOMPARE(visibleChildrenSpy.count(), 1);
+ QCOMPARE(childParentSpy.count(), 1);
+ QCOMPARE(childChildrenSpy.count(), 0);
+ QCOMPARE(childVisibleChildrenSpy.count(), 0);
+
+ grandChild->setParentItem(child.get());
+ QCOMPARE(childrenSpy.count(), 1);
+ QCOMPARE(visibleChildrenSpy.count(), 1);
+ QCOMPARE(childParentSpy.count(), 1);
+ QCOMPARE(childChildrenSpy.count(), 1);
+ QCOMPARE(childVisibleChildrenSpy.count(), 0);
+ QCOMPARE(grandChildParentSpy.count(), 1);
+
+ parent.reset();
+
+ QVERIFY(!child->parentItem());
+ QVERIFY(grandChild->parentItem());
+ QCOMPARE(childrenSpy.count(), 1);
+ QCOMPARE(visibleChildrenSpy.count(), 1);
+ QCOMPARE(childParentSpy.count(), 2);
+ QCOMPARE(grandChildParentSpy.count(), 1);
+}
+
+
QTEST_MAIN(tst_QQuickItem)
#include "tst_qquickitem.moc"
|