aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn Rutledge <shawn.rutledge@qt.io>2026-04-21 10:22:40 +0200
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2026-05-06 22:45:18 +0000
commit99c7c83a1d5969303a540a1ccf10da4a9cdbd0cb (patch)
treef692f143a86e33a7b0440ceb4dbab2e6cf0c4a4f
parent1204843b85dbcacffcc8c5c2da1685ce5787325b (diff)
QQuickDrag: fix binding loop and re-entrancy for Automatic drag type
When Drag.dragType is Automatic and Drag.active is bound to e.g. DragHandler.active, calling setActive(true) previously invoked startDrag() immediately, which in turn called QDrag::exec(). That exec() call enters a nested event loop, which is incompatible with being called from within a QML binding or JS expression: the QML engine is still mid-evaluation when exec() blocks, so when the pointer release arrives in the nested loop, deactivates the DragHandler, and causes the binding to re-trigger, the engine detects a binding loop. Fix this by deferring the startDrag() call via Qt::QueuedConnection so that the current JS frame unwinds completely before exec() runs from a clean event-loop context. An 'if (d->active)' guard in the deferred lambda handles the case where the drag is cancelled before the queued call fires. The executingNativeDrag flag is now set/cleared around exec() inside startDrag() itself (covering both the Automatic binding path and direct QML calls to Drag.startDrag()). It suppresses re-entrant setActive(false) calls that arrive via DragHandler deactivation while exec() is blocking, since startDrag() already handles all cleanup when exec() returns. Fixes: QTBUG-144006 Change-Id: I9bf6a4e6f3edea7e12e187b5756ab483c462696a Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io> Reviewed-by: Konsta Alajärvi <konsta.alajarvi@qt.io> Reviewed-by: Oliver Eftevaag <oliver.eftevaag@qt.io> (cherry picked from commit ae6e67e4cf23af6dbceab5e62881864b3a38c867) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r--src/quick/items/qquickdrag.cpp20
-rw-r--r--src/quick/items/qquickdrag_p_p.h2
2 files changed, 18 insertions, 4 deletions
diff --git a/src/quick/items/qquickdrag.cpp b/src/quick/items/qquickdrag.cpp
index c17027a2e4..316bbcddde 100644
--- a/src/quick/items/qquickdrag.cpp
+++ b/src/quick/items/qquickdrag.cpp
@@ -221,16 +221,26 @@ void QQuickDragAttached::setActive(bool active)
if (d->active != active) {
if (d->inEvent)
qmlWarning(this) << "active cannot be changed from within a drag event handler";
- else if (active) {
+ else if (d->executingNativeDrag) {
+ // QDrag::exec() is blocking in a nested event loop. Pointer release events
+ // processed there may deactivate the DragHandler and re-trigger this setter.
+ // Suppress: startDrag() already handles cleanup when exec() returns.
+ } else if (active) {
if (d->dragType == QQuickDrag::Internal) {
d->start(d->supportedActions);
} else {
d->active = true;
emit activeChanged();
if (d->dragType == QQuickDrag::Automatic) {
- // There are different semantics than start() since startDrag()
- // may be called after an internal drag is already started.
- d->startDrag(d->supportedActions);
+ // QDrag::exec() enters a nested event loop; calling it directly
+ // from a QML binding or JS expression would block the engine mid-
+ // evaluation. Defer to a queued call so the current JS frame
+ // unwinds completely before exec() runs from the event loop.
+ QMetaObject::invokeMethod(this, [this]() {
+ Q_D(QQuickDragAttached);
+ if (d->active)
+ d->startDrag(d->supportedActions);
+ }, Qt::QueuedConnection);
}
}
}
@@ -849,7 +859,9 @@ Qt::DropAction QQuickDragAttachedPrivate::startDrag(Qt::DropActions supportedAct
drag->setHotSpot(hotSpot.toPoint());
emit q->dragStarted();
+ executingNativeDrag = true;
Qt::DropAction dropAction = drag->exec(supportedActions);
+ executingNativeDrag = false;
if (!QGuiApplicationPrivate::platformIntegration()->drag()->ownsDragObject())
drag->deleteLater();
diff --git a/src/quick/items/qquickdrag_p_p.h b/src/quick/items/qquickdrag_p_p.h
index 06b6547951..23d1fa7be7 100644
--- a/src/quick/items/qquickdrag_p_p.h
+++ b/src/quick/items/qquickdrag_p_p.h
@@ -49,6 +49,7 @@ public:
, active(false)
, listening(false)
, inEvent(false)
+ , executingNativeDrag(false)
, dragRestarted(false)
, itemMoved(false)
, eventQueued(false)
@@ -83,6 +84,7 @@ public:
bool active : 1;
bool listening : 1;
bool inEvent : 1;
+ bool executingNativeDrag : 1;
bool dragRestarted : 1;
bool itemMoved : 1;
bool eventQueued : 1;