Fix crash when the focus widget gets a focus proxy after the fact

QApplicationPrivate::focus_widget became a dangling pointer
in the following scenario:

A widget first gets focus and later on gets a focus proxy.
QApplicationPrivate::focus_widget was still pointing to the initial widget.
Upon destruction, QWidget::hasFocus() [which follows to the focus proxy
and then compares with focus_widget] was therefore false for both
widgets. So QWidget::clearFocus() didn't call
QApplicationPrivate::setFocusWidget(0) for either of them. As a
result, focus_widget remained set, and became dangling.

In real life, this happened with a QWebEngineView, which the application
gave focus to upon creation. At that time it doesn't have a focus proxy
yet. That happens later, in QWebEngineViewPrivate::widgetChanged.

https://bugs.kde.org/show_bug.cgi?id=381793

Change-Id: Ifee610bb76a2d4d2797b98ece9bffe5fffe3c6a6
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
David Faure 2019-07-29 14:27:16 +02:00
parent f53fc76060
commit 3e7463411e
2 changed files with 57 additions and 0 deletions

View File

@ -6436,8 +6436,18 @@ void QWidget::setFocusProxy(QWidget * w)
}
}
QWidget *oldDeepestFocusProxy = d_func()->deepestFocusProxy();
if (!oldDeepestFocusProxy)
oldDeepestFocusProxy = this;
const bool changingAppFocusWidget = (QApplicationPrivate::focus_widget == oldDeepestFocusProxy);
d->createExtra();
d->extra->focus_proxy = w;
if (changingAppFocusWidget) {
QWidget *newDeepestFocusProxy = d_func()->deepestFocusProxy();
QApplicationPrivate::focus_widget = newDeepestFocusProxy ? newDeepestFocusProxy : this;
}
}

View File

@ -181,6 +181,8 @@ private slots:
void tabOrderWithCompoundWidgets();
void tabOrderNoChange();
void tabOrderNoChange2();
void appFocusWidgetWithFocusProxyLater();
void appFocusWidgetWhenLosingFocusProxy();
#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
void activation();
#endif
@ -2031,6 +2033,51 @@ void tst_QWidget::tabOrderNoChange2()
QCOMPARE(focusChainBackward, getFocusChain(&w, false));
}
void tst_QWidget::appFocusWidgetWithFocusProxyLater()
{
// Given a lineedit without a focus proxy
QWidget window;
window.setWindowTitle(QTest::currentTestFunction());
QLineEdit *lineEditFocusProxy = new QLineEdit(&window);
QLineEdit *lineEdit = new QLineEdit(&window);
lineEdit->setFocus();
window.show();
QApplication::setActiveWindow(&window);
QVERIFY(QTest::qWaitForWindowActive(&window));
QCOMPARE(QApplication::focusWidget(), lineEdit);
// When setting a focus proxy for the focus widget (like QWebEngineView does)
lineEdit->setFocusProxy(lineEditFocusProxy);
// Then the focus widget should be updated
QCOMPARE(QApplication::focusWidget(), lineEditFocusProxy);
// So that deleting the lineEdit and later the window, doesn't crash
delete lineEdit;
QCOMPARE(QApplication::focusWidget(), nullptr);
}
void tst_QWidget::appFocusWidgetWhenLosingFocusProxy()
{
// Given a lineedit with a focus proxy
QWidget window;
window.setWindowTitle(QTest::currentTestFunction());
QLineEdit *lineEditFocusProxy = new QLineEdit(&window);
QLineEdit *lineEdit = new QLineEdit(&window);
lineEdit->setFocusProxy(lineEditFocusProxy);
lineEdit->setFocus();
window.show();
QApplication::setActiveWindow(&window);
QVERIFY(QTest::qWaitForWindowActive(&window));
QCOMPARE(QApplication::focusWidget(), lineEditFocusProxy);
// When unsetting the focus proxy
lineEdit->setFocusProxy(nullptr);
// Then the application focus widget should be back to the lineedit
QCOMPARE(QApplication::focusWidget(), lineEdit);
}
#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
void tst_QWidget::activation()
{