QCocoaMenu: fix crash when app quits while menu is open

Consider the following QML code:

    import QtQuick
    import QtQuick.Controls

    ApplicationWindow {
        visible: true

        Menu {
            id: menu
            MenuItem {
                text: "Some action"
            }
        }

        TapHandler {
            acceptedButtons: Qt.RightButton
            onTapped: {
                exitAppTimer.start()
                menu.open()
            }
        }

        Timer {
            id: exitAppTimer
            interval: 1000
            onTriggered: Qt.quit()
        }
    }

With the new native Qt Quick Menu, this will create a native menu on
platforms like macOS. When the user right clicks on the window, a timer
is started and a native menu opened. After 1 second, Qt.quit() is
called while the menu is still open. As popUpContextMenu is blocking,
when the menu is finally closed (by user interaction), control returns
to QCocoaMenu::showPopup, but the QCocoaWindow has since been
destroyed.

Account for this by storing the window in a QPointer.

It's not possible to test this as native menus can't be auto-tested.

Fixes: QTBUG-124551
Pick-to: 6.5
Change-Id: I14a97073f350c38828b3e16bb157439aeeeb6529
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
(cherry picked from commit 468cb035efe4890c388069eb373a7ae8ef178146)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Mitch Curtis 2024-04-24 12:31:26 +08:00 committed by Qt Cherry-pick Bot
parent fa8199459d
commit bab1773661

View File

@ -326,7 +326,9 @@ void QCocoaMenu::showPopup(const QWindow *parentWindow, const QRect &targetRect,
QPointer<QCocoaMenu> guard = this;
QPoint pos = QPoint(targetRect.left(), targetRect.top() + targetRect.height());
QCocoaWindow *cocoaWindow = parentWindow ? static_cast<QCocoaWindow *>(parentWindow->handle()) : nullptr;
// If the app quits while the menu is open (e.g. through a timer that starts before the menu was opened),
// then the window will have been destroyed before this function finishes executing. Account for that with QPointer.
QPointer<QCocoaWindow> cocoaWindow = parentWindow ? static_cast<QCocoaWindow *>(parentWindow->handle()) : nullptr;
NSView *view = cocoaWindow ? cocoaWindow->view() : nil;
NSMenuItem *nsItem = item ? ((QCocoaMenuItem *)item)->nsItem() : nil;