QMenu: don't crash when another popup is closed when a popup is closed

When closing a popup (submenu) triggers closing another popup (the menu)
programatically it can happen that QApplicationPrivate::popupWidgets is
destroyed. Therefore we have to check if popupWidgets is still valid
after the focus change event was delivered.

Fixes: QTBUG-81222
Change-Id: Ide3a6897e43f389d396a80d8b158f7c8eb04e3aa
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
This commit is contained in:
Christian Ehrlicher 2020-01-10 22:11:12 +01:00
parent a11267c532
commit 5c3b5efd40
2 changed files with 59 additions and 1 deletions

View File

@ -3762,7 +3762,9 @@ void QApplicationPrivate::closePopup(QWidget *popup)
if (QWidget *fw = aw->focusWidget())
fw->setFocus(Qt::PopupFocusReason);
if (QApplicationPrivate::popupWidgets->count() == 1) // grab mouse/keyboard
// can become nullptr due to setFocus() above
if (QApplicationPrivate::popupWidgets &&
QApplicationPrivate::popupWidgets->count() == 1) // grab mouse/keyboard
grabForPopup(aw);
}

View File

@ -40,6 +40,7 @@
#include <QWidgetAction>
#include <QDesktopWidget>
#include <QScreen>
#include <QSpinBox>
#include <qdialog.h>
#include <qmenu.h>
@ -114,6 +115,7 @@ private slots:
void QTBUG30595_rtl_submenu();
void QTBUG20403_nested_popup_on_shortcut_trigger();
void QTBUG47515_widgetActionEnterLeave();
void QTBUG8122_widgetActionCrashOnClose();
void QTBUG_10735_crashWithDialog();
#ifdef Q_OS_MAC
@ -1352,6 +1354,60 @@ void tst_QMenu::QTBUG47515_widgetActionEnterLeave()
}
}
void tst_QMenu::QTBUG8122_widgetActionCrashOnClose()
{
if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation))
QSKIP("Window activation is not supported");
if (QGuiApplication::platformName() == QLatin1String("cocoa"))
QSKIP("See QTBUG-63031");
#ifdef Q_OS_WINRT
QSKIP("WinRT does not support QTest::mouseMove");
#endif
const QRect availableGeometry = QGuiApplication::primaryScreen()->availableGeometry();
QRect geometry(QPoint(), availableGeometry.size() / 3);
geometry.moveCenter(availableGeometry.center());
QPoint pointOutsideMenu = geometry.bottomRight() - QPoint(5, 5);
QMainWindow topLevel;
topLevel.setGeometry(geometry);
auto menuBar = topLevel.menuBar();
auto menu = menuBar->addMenu("Menu");
auto wAct = new QWidgetAction(menu);
auto spinBox1 = new QSpinBox(menu);
wAct->setDefaultWidget(spinBox1);
menu->addAction(wAct);
auto subMenu = menu->addMenu("Submenu");
auto nextMenuAct = menu->addMenu(subMenu);
auto wAct2 = new QWidgetAction(menu);
auto spinBox2 = new QSpinBox(menu);
wAct2->setDefaultWidget(spinBox2);
subMenu->addAction(wAct2);
QObject::connect(spinBox2, &QSpinBox::editingFinished, menu, &QMenu::hide);
topLevel.show();
topLevel.setWindowTitle(QTest::currentTestFunction());
QVERIFY(QTest::qWaitForWindowActive(&topLevel));
QWindow *topLevelWindow = topLevel.windowHandle();
QVERIFY(topLevelWindow);
const QPoint menuActionPos = menuBar->mapTo(&topLevel, menuBar->actionGeometry(menu->menuAction()).center());
QTest::mouseClick(topLevelWindow, Qt::LeftButton, Qt::KeyboardModifiers(), menuActionPos);
QVERIFY(QTest::qWaitForWindowExposed(menu));
QPoint w1Center = topLevel.mapFromGlobal(spinBox1->mapToGlobal(spinBox1->rect().center()));
QTest::mouseClick(topLevelWindow, Qt::LeftButton, Qt::KeyboardModifiers(), w1Center);
menu->setActiveAction(nextMenuAct);
QVERIFY(QTest::qWaitForWindowExposed(subMenu));
QPoint w2Center = topLevel.mapFromGlobal(spinBox2->mapToGlobal(spinBox2->rect().center()));
QTest::mouseClick(topLevelWindow, Qt::LeftButton, Qt::KeyboardModifiers(), w2Center);
QTest::mouseMove(topLevelWindow, topLevel.mapFromGlobal(pointOutsideMenu));
QTRY_VERIFY(menu->isHidden());
}
class MyMenu : public QMenu
{
Q_OBJECT