diff options
| -rw-r--r-- | src/quick/items/qquicklistview.cpp | 4 | ||||
| -rw-r--r-- | tests/auto/quick/qquicklistview/data/orphanedSectionContext.qml | 42 | ||||
| -rw-r--r-- | tests/auto/quick/qquicklistview/tst_qquicklistview.cpp | 44 |
3 files changed, 90 insertions, 0 deletions
diff --git a/src/quick/items/qquicklistview.cpp b/src/quick/items/qquicklistview.cpp index 29eec9cb8c..1f7943af7c 100644 --- a/src/quick/items/qquicklistview.cpp +++ b/src/quick/items/qquicklistview.cpp @@ -2113,6 +2113,10 @@ bool QQuickListViewPrivate::flick(AxisData &data, qreal minExtent, qreal maxExte void QQuickListViewPrivate::setSectionHelper(QQmlContext *context, QQuickItem *sectionItem, const QString §ion) { + // The section item can outlive its context + // Skip the update rather than dereference the null context + if (!context) + return; if (!QQmlContextData::get(context)->isInternal() && context->contextProperty(QLatin1String("section")).isValid()) context->setContextProperty(QLatin1String("section"), section); else diff --git a/tests/auto/quick/qquicklistview/data/orphanedSectionContext.qml b/tests/auto/quick/qquicklistview/data/orphanedSectionContext.qml new file mode 100644 index 0000000000..e74cea70a3 --- /dev/null +++ b/tests/auto/quick/qquicklistview/data/orphanedSectionContext.qml @@ -0,0 +1,42 @@ +import QtQuick + +ListView { + id: listView + + width: 320 + height: 480 + + model: ListModel { + id: listModel + Component.onCompleted: { + for (let i = 0; i < 50; ++i) + listModel.append({ name: "item " + i, sectionStr: "section " + (i % 5) }) + } + } + + property int sectionCount: 0 + + section.property: "sectionStr" + section.criteria: ViewSection.FullString + section.labelPositioning: ViewSection.InlineLabels | ViewSection.CurrentLabelAtStart + + // The section delegate is bound, so section items reuse the ListView's own + // context (see tst_QQuickListView::orphanedSectionContext()). + section.delegate: Rectangle { + width: listView.width + height: 30 + color: "#E0E1D8" + Text { + anchors.centerIn: parent + font.pixelSize: 12 + text: section + } + Component.onCompleted: ++listView.sectionCount + } + + delegate: Item { + width: listView.width + height: 40 + Text { text: name } + } +} diff --git a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp index c30f5e5660..fbf482e3d6 100644 --- a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp +++ b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp @@ -126,6 +126,7 @@ private slots: void sectionsItemInsertion(); void sectionsFocusChain(); void removeSectionsOnNonvisibleItems(); + void orphanedSectionContext(); void cacheBuffer(); void positionViewAtBeginningEnd(); void positionViewAtBeginningWithSections_data(); @@ -2887,6 +2888,49 @@ void tst_QQuickListView::removeSectionsOnNonvisibleItems() verifySectionData(listView, ++verifySectionId); } +void tst_QQuickListView::orphanedSectionContext() +{ + // QTBUG-130819: when the section delegate is bound, section items reuse + // the ListView's own context. If that context's parent context is + // destroyed, the section items are left with a null parent context, and + // setSectionHelper() must not dereference it. + QQmlEngine engine; + QQmlContext *outerContext = new QQmlContext(engine.rootContext()); + QQmlContext *innerContext = new QQmlContext(outerContext); + + QQmlComponent component(&engine, testFileUrl("orphanedSectionContext.qml")); + QScopedPointer<QObject> root(component.create(innerContext)); + QVERIFY2(root, qPrintable(component.errorString())); + auto *listView = qobject_cast<QQuickListView *>(root.data()); + QVERIFY(listView != nullptr); + + QQuickWindow window; + window.resize(listView->width(), listView->height()); + listView->setParentItem(window.contentItem()); + window.show(); + QVERIFY(QTest::qWaitForWindowExposed(&window)); + + QTRY_COMPARE_GT(listView->property("sectionCount").toInt(), 0); + + delete outerContext; + + // Any relayout after the context was destroyed calls setSectionHelper() + // with a null parent context. + QQuickItem *firstSection = nullptr; + const QList<QQuickItem *> children = listView->contentItem()->childItems(); + for (QQuickItem *child : children) { + if (qFuzzyCompare(child->y(), qreal(70.0)) && qFuzzyCompare(child->height(), qreal(30.0)) + && child->isVisible()) { + firstSection = child; + break; + } + } + QVERIFY(firstSection != nullptr); + + listView->setContentY(800); + QTRY_COMPARE(firstSection->isVisible(), false); +} + void tst_QQuickListView::currentIndex_delayedItemCreation() { QFETCH(bool, setCurrentToZero); |
