diff options
| author | Shawn Rutledge <shawn.rutledge@qt.io> | 2026-04-21 10:22:40 +0200 |
|---|---|---|
| committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2026-05-06 22:45:18 +0000 |
| commit | 99c7c83a1d5969303a540a1ccf10da4a9cdbd0cb (patch) | |
| tree | f692f143a86e33a7b0440ceb4dbab2e6cf0c4a4f | |
| parent | 1204843b85dbcacffcc8c5c2da1685ce5787325b (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.cpp | 20 | ||||
| -rw-r--r-- | src/quick/items/qquickdrag_p_p.h | 2 |
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; |
