2022-07-13 12:31:15 +00:00
|
|
|
|
From aca0c736c99439a8f4d297bc57c1fa5a2d2a6e88 Mon Sep 17 00:00:00 2001
|
2022-05-17 06:19:51 +00:00
|
|
|
|
From: Fabian Kosmale <fabian.kosmale@qt.io>
|
|
|
|
|
Date: Wed, 4 May 2022 09:10:54 +0200
|
2022-07-13 12:31:15 +00:00
|
|
|
|
Subject: [PATCH 15/19] QQuickItem: Guard against cycles in
|
2022-05-17 06:19:51 +00:00
|
|
|
|
nextPrevItemInTabFocusChain
|
|
|
|
|
MIME-Version: 1.0
|
|
|
|
|
Content-Type: text/plain; charset=UTF-8
|
|
|
|
|
Content-Transfer-Encoding: 8bit
|
|
|
|
|
|
|
|
|
|
nextPrevItemInTabFocusChain already had a check to prevent running into
|
|
|
|
|
cycles, it would however only detect if we reached the original item. If
|
|
|
|
|
our cycle instead would loop between reachable items without ever
|
|
|
|
|
returning to the initial one, as in the diagram below, then we would
|
|
|
|
|
never terminate the loop.
|
|
|
|
|
|
|
|
|
|
/-->other item<---next item
|
|
|
|
|
initial-item \ ^
|
|
|
|
|
\ |
|
|
|
|
|
--->different item
|
|
|
|
|
|
|
|
|
|
To prevent this from happening, we keep track of all items we've seen so
|
|
|
|
|
far. One last complications arises due to the fact that we do visit the
|
|
|
|
|
parent twice under some cicrcumstances, but we already have the skip
|
|
|
|
|
variable to indicate that case – we simply skip the duplicate check if
|
|
|
|
|
it is set to true.
|
|
|
|
|
|
|
|
|
|
Pick-to: 6.2 6.3
|
|
|
|
|
Fixes: QTBUG-87190
|
|
|
|
|
Change-Id: I1449a7ebf8f325f00c296e8a8db4360faf1049e4
|
|
|
|
|
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
|
|
|
|
|
(cherry picked from commit e74bcf751495d9fe27efd195bc04e2a6ae6732a4)
|
|
|
|
|
---
|
|
|
|
|
src/quick/items/qquickitem.cpp | 7 ++++++-
|
|
|
|
|
.../data/activeFocusOnTab_infiniteLoop3.qml | 13 +++++++++++++
|
|
|
|
|
tests/auto/quick/qquickitem2/tst_qquickitem.cpp | 12 ++++++++++++
|
|
|
|
|
3 files changed, 31 insertions(+), 1 deletion(-)
|
|
|
|
|
create mode 100644 tests/auto/quick/qquickitem2/data/activeFocusOnTab_infiniteLoop3.qml
|
|
|
|
|
|
|
|
|
|
diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp
|
2022-07-13 12:31:15 +00:00
|
|
|
|
index 3df899d63d..83e52b12e5 100644
|
2022-05-17 06:19:51 +00:00
|
|
|
|
--- a/src/quick/items/qquickitem.cpp
|
|
|
|
|
+++ b/src/quick/items/qquickitem.cpp
|
|
|
|
|
@@ -59,6 +59,7 @@
|
|
|
|
|
#include <QtCore/private/qnumeric_p.h>
|
|
|
|
|
#include <QtGui/qpa/qplatformtheme.h>
|
|
|
|
|
#include <QtCore/qloggingcategory.h>
|
|
|
|
|
+#include <QtCore/private/qduplicatetracker_p.h>
|
|
|
|
|
|
|
|
|
|
#include <private/qqmlglobal_p.h>
|
|
|
|
|
#include <private/qqmlengine_p.h>
|
2022-07-13 12:31:15 +00:00
|
|
|
|
@@ -2526,6 +2527,7 @@ QQuickItem* QQuickItemPrivate::nextPrevItemInTabFocusChain(QQuickItem *item, boo
|
2022-05-17 06:19:51 +00:00
|
|
|
|
QQuickItem *current = item;
|
|
|
|
|
qCDebug(DBG_FOCUS) << "QQuickItemPrivate::nextPrevItemInTabFocusChain: startItem:" << startItem;
|
|
|
|
|
qCDebug(DBG_FOCUS) << "QQuickItemPrivate::nextPrevItemInTabFocusChain: firstFromItem:" << firstFromItem;
|
|
|
|
|
+ QDuplicateTracker<QQuickItem *> cycleDetector;
|
|
|
|
|
do {
|
|
|
|
|
qCDebug(DBG_FOCUS) << "QQuickItemPrivate::nextPrevItemInTabFocusChain: current:" << current;
|
|
|
|
|
qCDebug(DBG_FOCUS) << "QQuickItemPrivate::nextPrevItemInTabFocusChain: from:" << from;
|
2022-07-13 12:31:15 +00:00
|
|
|
|
@@ -2592,7 +2594,10 @@ QQuickItem* QQuickItemPrivate::nextPrevItemInTabFocusChain(QQuickItem *item, boo
|
2022-05-17 06:19:51 +00:00
|
|
|
|
// traversed all of the chain (by compare the [current] item with [startItem])
|
|
|
|
|
// Since the [startItem] might be promoted to its parent if it is invisible,
|
|
|
|
|
// we still have to check [current] item with original start item
|
|
|
|
|
- if ((current == startItem || current == originalStartItem) && from == firstFromItem) {
|
|
|
|
|
+ // We might also run into a cycle before we reach firstFromItem again
|
|
|
|
|
+ // but note that we have to ignore current if we are meant to skip it
|
|
|
|
|
+ if (((current == startItem || current == originalStartItem) && from == firstFromItem) ||
|
|
|
|
|
+ (!skip && cycleDetector.hasSeen(current))) {
|
|
|
|
|
// wrapped around, avoid endless loops
|
|
|
|
|
if (item == contentItem) {
|
|
|
|
|
qCDebug(DBG_FOCUS) << "QQuickItemPrivate::nextPrevItemInTabFocusChain: looped, return contentItem";
|
|
|
|
|
diff --git a/tests/auto/quick/qquickitem2/data/activeFocusOnTab_infiniteLoop3.qml b/tests/auto/quick/qquickitem2/data/activeFocusOnTab_infiniteLoop3.qml
|
|
|
|
|
new file mode 100644
|
|
|
|
|
index 0000000000..889e480f3b
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/tests/auto/quick/qquickitem2/data/activeFocusOnTab_infiniteLoop3.qml
|
|
|
|
|
@@ -0,0 +1,13 @@
|
|
|
|
|
+import QtQuick 2.6
|
|
|
|
|
+
|
|
|
|
|
+Item {
|
|
|
|
|
+ visible: true
|
|
|
|
|
+ Item {
|
|
|
|
|
+ visible: false
|
|
|
|
|
+ Item {
|
|
|
|
|
+ objectName: "hiddenChild"
|
|
|
|
|
+ activeFocusOnTab: true
|
|
|
|
|
+ focus: true
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
diff --git a/tests/auto/quick/qquickitem2/tst_qquickitem.cpp b/tests/auto/quick/qquickitem2/tst_qquickitem.cpp
|
|
|
|
|
index f65650cf9c..eeff768bb4 100644
|
|
|
|
|
--- a/tests/auto/quick/qquickitem2/tst_qquickitem.cpp
|
|
|
|
|
+++ b/tests/auto/quick/qquickitem2/tst_qquickitem.cpp
|
|
|
|
|
@@ -66,6 +66,7 @@ private slots:
|
|
|
|
|
void activeFocusOnTab10();
|
|
|
|
|
void activeFocusOnTab_infiniteLoop_data();
|
|
|
|
|
void activeFocusOnTab_infiniteLoop();
|
|
|
|
|
+ void activeFocusOnTab_infiniteLoopControls();
|
|
|
|
|
|
|
|
|
|
void nextItemInFocusChain();
|
|
|
|
|
void nextItemInFocusChain2();
|
|
|
|
|
@@ -1055,6 +1056,17 @@ void tst_QQuickItem::activeFocusOnTab_infiniteLoop()
|
|
|
|
|
QCOMPARE(item, window->rootObject());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
+
|
|
|
|
|
+void tst_QQuickItem::activeFocusOnTab_infiniteLoopControls()
|
|
|
|
|
+{
|
|
|
|
|
+ auto source = testFileUrl("activeFocusOnTab_infiniteLoop3.qml");
|
|
|
|
|
+ QScopedPointer<QQuickView>window(new QQuickView());
|
|
|
|
|
+ window->setSource(source);
|
|
|
|
|
+ window->show();
|
|
|
|
|
+ QVERIFY(window->errors().isEmpty());
|
|
|
|
|
+ QTest::keyClick(window.get(), Qt::Key_Tab); // should not hang
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
void tst_QQuickItem::nextItemInFocusChain()
|
|
|
|
|
{
|
|
|
|
|
if (!qt_tab_all_widgets())
|
|
|
|
|
--
|
|
|
|
|
2.36.1
|
|
|
|
|
|