macOS: work around getting invalid result from NSAlert::runModal

It is possible for an application to trigger the unexpected result of
NSModalResponseContinue from our call to NSAlert::runModal, by showing
and hiding a modal dialog first, and processing events explicitly. This
at best only flashes the native message box, at worst it triggers an
assert from our processResponse function evaluating that response value
as impossible (via Q_UNREACHABLE).

We should never call processResponse with NSModalResponseContinue,
but instead keep the modal loop running and the dialog visible.
So introduce a wrapper to NSAlert::runModal that keeps calling that
method until we get a result other than NSModalResponseContinue.

Writing an auto test for this failed; a simple test didn't reproduce the
assert; trying to place the opening of the native message box into a
lambda that would be called by the event loop (simulating the button
press from the bug report's reproducer) resulted in the native message
box never closing and the test blocking (and still not triggering the
assert).

Fixes: QTBUG-114546
Change-Id: Iab25eff55c48b103287d1881ac355e6cdd190f7a
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
(cherry picked from commit 2c458a82213ae568ad708e07ee9ed5f4e494a800)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Volker Hilsheimer 2023-06-18 13:27:57 +02:00 committed by Qt Cherry-pick Bot
parent 15de36f97b
commit 848c017165
2 changed files with 17 additions and 2 deletions

View File

@ -28,6 +28,7 @@ private:
Qt::WindowModality modality() const;
NSAlert *m_alert = nullptr;
QEventLoop *m_eventLoop = nullptr;
NSModalResponse runModal() const;
void processResponse(NSModalResponse response);
};

View File

@ -222,7 +222,7 @@ bool QCocoaMessageDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality w
if (m_alert && NSApp.modalWindow != m_alert.window) {
qCDebug(lcQpaDialogs) << "Running deferred modal" << m_alert;
QCocoaEventDispatcher::clearCurrentThreadCocoaEventDispatcherInterruptFlag();
processResponse([m_alert runModal]);
processResponse(runModal());
}
});
}
@ -230,6 +230,20 @@ bool QCocoaMessageDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality w
return true;
}
// We shouldn't get NSModalResponseContinue as a response from NSAlert::runModal,
// and processResponse must not be called with that value (if we are there, it's
// too late to do anything about it.
// However, as QTBUG-114546 shows, there are scenarios where we might get that
// response anyway. We interpret it to keep the modal loop running, and we only
// return if we got something else to pass to processResponse.
NSModalResponse QCocoaMessageDialog::runModal() const
{
NSModalResponse response = NSModalResponseContinue;
while (response == NSModalResponseContinue)
response = [m_alert runModal];
return response;
}
void QCocoaMessageDialog::exec()
{
Q_ASSERT(m_alert);
@ -242,7 +256,7 @@ void QCocoaMessageDialog::exec()
} else {
qCDebug(lcQpaDialogs) << "Running modal" << m_alert;
QCocoaEventDispatcher::clearCurrentThreadCocoaEventDispatcherInterruptFlag();
processResponse([m_alert runModal]);
processResponse(runModal());
}
}