From 3902b27ee40400db6cf596ca0db31b6497f0421b Mon Sep 17 00:00:00 2001 From: Joerg Bornemann Date: Mon, 10 Apr 2017 13:47:20 +0200 Subject: Override shortcuts in HTML input fields When users defined a single-letter short cut it was not possible to type this letter in HTML input fields. Fix this by accepting ShortcutOverride events whenever the web page is editing text. Use QInputControl::isCommonTextEditShortcut for Qt 5.9 and later. For the case where QtWebEngine is built against an older Qt a duplicated code path is used. Also, ensure users do not override web action short cuts. Task-number: QTBUG-59053 Change-Id: Ic26cf2a040a72b118273c6645c00b2913b995b0b Reviewed-by: Qt CI Bot Reviewed-by: Alexandru Croitor --- src/core/core_chromium.pri | 1 + src/core/render_widget_host_view_qt_delegate.cpp | 113 +++++++++++++++++++++ src/core/render_widget_host_view_qt_delegate.h | 2 + .../render_widget_host_view_qt_delegate_quick.cpp | 8 +- .../render_widget_host_view_qt_delegate_widget.cpp | 21 +++- .../widgets/qwebengineview/tst_qwebengineview.cpp | 58 +++++++++++ 6 files changed, 198 insertions(+), 5 deletions(-) create mode 100644 src/core/render_widget_host_view_qt_delegate.cpp diff --git a/src/core/core_chromium.pri b/src/core/core_chromium.pri index 9c1e9f0db..f13095bfe 100644 --- a/src/core/core_chromium.pri +++ b/src/core/core_chromium.pri @@ -79,6 +79,7 @@ SOURCES = \ qrc_protocol_handler_qt.cpp \ render_view_observer_host_qt.cpp \ render_widget_host_view_qt.cpp \ + render_widget_host_view_qt_delegate.cpp \ renderer/content_renderer_client_qt.cpp \ renderer/render_frame_observer_qt.cpp \ renderer/render_view_observer_qt.cpp \ diff --git a/src/core/render_widget_host_view_qt_delegate.cpp b/src/core/render_widget_host_view_qt_delegate.cpp new file mode 100644 index 000000000..a86900433 --- /dev/null +++ b/src/core/render_widget_host_view_qt_delegate.cpp @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "render_widget_host_view_qt_delegate.h" + +#include +#include + +#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) +#include +#endif + +static bool isCommonTextEditShortcut(const QKeyEvent *ke) +{ +#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) + return QInputControl::isCommonTextEditShortcut(ke); +#else + if (ke->modifiers() == Qt::NoModifier + || ke->modifiers() == Qt::ShiftModifier + || ke->modifiers() == Qt::KeypadModifier) { + if (ke->key() < Qt::Key_Escape) { + return true; + } else { + switch (ke->key()) { + case Qt::Key_Return: + case Qt::Key_Enter: + case Qt::Key_Delete: + case Qt::Key_Home: + case Qt::Key_End: + case Qt::Key_Backspace: + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_Tab: + return true; + default: + break; + } + } + } else if (ke->matches(QKeySequence::Copy) + || ke->matches(QKeySequence::Paste) + || ke->matches(QKeySequence::Cut) + || ke->matches(QKeySequence::Redo) + || ke->matches(QKeySequence::Undo) + || ke->matches(QKeySequence::MoveToNextWord) + || ke->matches(QKeySequence::MoveToPreviousWord) + || ke->matches(QKeySequence::MoveToStartOfDocument) + || ke->matches(QKeySequence::MoveToEndOfDocument) + || ke->matches(QKeySequence::SelectNextWord) + || ke->matches(QKeySequence::SelectPreviousWord) + || ke->matches(QKeySequence::SelectStartOfLine) + || ke->matches(QKeySequence::SelectEndOfLine) + || ke->matches(QKeySequence::SelectStartOfBlock) + || ke->matches(QKeySequence::SelectEndOfBlock) + || ke->matches(QKeySequence::SelectStartOfDocument) + || ke->matches(QKeySequence::SelectEndOfDocument) + || ke->matches(QKeySequence::SelectAll) + ) { + return true; + } + return false; +#endif +} + +namespace QtWebEngineCore { + +bool RenderWidgetHostViewQtDelegateClient::handleShortcutOverrideEvent(QKeyEvent *event) +{ + if (inputMethodQuery(Qt::ImEnabled).toBool() && isCommonTextEditShortcut(event)) { + event->accept(); + return true; + } + return false; +} + +} // namespace QtWebEngineCore diff --git a/src/core/render_widget_host_view_qt_delegate.h b/src/core/render_widget_host_view_qt_delegate.h index 6286596c6..dda59a01a 100644 --- a/src/core/render_widget_host_view_qt_delegate.h +++ b/src/core/render_widget_host_view_qt_delegate.h @@ -48,6 +48,7 @@ QT_BEGIN_NAMESPACE class QCursor; class QEvent; +class QKeyEvent; class QPainter; class QSGLayer; class QSGNode; @@ -85,6 +86,7 @@ public: virtual void windowChanged() = 0; virtual bool forwardEvent(QEvent *) = 0; virtual QVariant inputMethodQuery(Qt::InputMethodQuery query) = 0; + virtual bool handleShortcutOverrideEvent(QKeyEvent *event); }; class QWEBENGINE_EXPORT RenderWidgetHostViewQtDelegate { diff --git a/src/webengine/render_widget_host_view_qt_delegate_quick.cpp b/src/webengine/render_widget_host_view_qt_delegate_quick.cpp index b3348b43e..749a2e0d8 100644 --- a/src/webengine/render_widget_host_view_qt_delegate_quick.cpp +++ b/src/webengine/render_widget_host_view_qt_delegate_quick.cpp @@ -241,10 +241,12 @@ void RenderWidgetHostViewQtDelegateQuick::inputMethodStateChanged(bool editorVis bool RenderWidgetHostViewQtDelegateQuick::event(QEvent *event) { if (event->type() == QEvent::ShortcutOverride) { - if (editorActionForKeyEvent(static_cast(event)) != QQuickWebEngineView::NoWebAction) { - event->accept(); + QKeyEvent *keyEvent = static_cast(event); + if (m_client->handleShortcutOverrideEvent(keyEvent)) return true; - } + if (editorActionForKeyEvent(keyEvent) != QQuickWebEngineView::NoWebAction) + event->accept(); + return true; } if (event->type() == QEvent::NativeGesture) diff --git a/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.cpp b/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.cpp index fd58a0708..c608ba2aa 100644 --- a/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.cpp +++ b/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.cpp @@ -59,6 +59,15 @@ namespace QtWebEngineCore { +static bool handleShortcutOverrideEvent(RenderWidgetHostViewQtDelegateClient *client, QKeyEvent *ke) +{ + if (client->handleShortcutOverrideEvent(ke)) + return true; + if (editorActionForKeyEvent(ke) != QWebEnginePage::NoWebAction) + ke->accept(); + return true; +} + class RenderWidgetHostViewQuickItem : public QQuickItem { public: RenderWidgetHostViewQuickItem(RenderWidgetHostViewQtDelegateClient *client) : m_client(client) @@ -68,6 +77,14 @@ public: setFocus(true); } protected: + bool event(QEvent *event) override + { + if (event->type() == QEvent::ShortcutOverride) { + handleShortcutOverrideEvent(m_client, static_cast(event)); + return true; + } + return QQuickItem::event(event); + } void focusInEvent(QFocusEvent *event) override { m_client->forwardEvent(event); @@ -437,8 +454,8 @@ bool RenderWidgetHostViewQtDelegateWidget::event(QEvent *event) // We forward focus events later, once they have made it to the m_rootItem. return QQuickWidget::event(event); case QEvent::ShortcutOverride: - if (editorActionForKeyEvent(static_cast(event)) != QWebEnginePage::NoWebAction) { - event->accept(); + if (event->type() == QEvent::ShortcutOverride) { + handleShortcutOverrideEvent(m_client, static_cast(event)); return true; } break; diff --git a/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp b/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp index 78190622c..ce88ace16 100644 --- a/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp +++ b/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #define VERIFY_INPUTMETHOD_HINTS(actual, expect) \ QVERIFY(actual == expect); @@ -95,6 +96,7 @@ private Q_SLOTS: void keyboardEvents(); void keyboardFocusAfterPopup(); void postData(); + void inputFieldOverridesShortcuts(); void softwareInputPanel(); void inputMethods(); @@ -1302,6 +1304,62 @@ void tst_QWebEngineView::postData() server.close(); } +void tst_QWebEngineView::inputFieldOverridesShortcuts() +{ + bool actionTriggered = false; + QAction *action = new QAction; + action->setShortcut(Qt::Key_X); + connect(action, &QAction::triggered, [&actionTriggered] () { actionTriggered = true; }); + + QWebEngineView view; + view.addAction(action); + + QSignalSpy loadFinishedSpy(&view, SIGNAL(loadFinished(bool))); + view.setHtml(QString("" + "" + "" + "")); + QVERIFY(loadFinishedSpy.wait()); + + view.show(); + QTest::qWaitForWindowActive(&view); + + auto inputFieldValue = [&view] () -> QString { + return evaluateJavaScriptSync(view.page(), + "input1.value").toString(); + }; + + // The input form is not focused. The action is triggered on pressing X. + QTest::keyClick(view.windowHandle(), Qt::Key_X); + QTRY_VERIFY(actionTriggered); + QCOMPARE(inputFieldValue(), QString("x")); + + // The input form is focused. The action is not triggered, and the form's text changed. + evaluateJavaScriptSync(view.page(), "input1.focus();"); + actionTriggered = false; + QTest::keyClick(view.windowHandle(), Qt::Key_Y); + QTRY_COMPARE(inputFieldValue(), QString("yx")); + QVERIFY(!actionTriggered); + + // The input form is focused. Make sure we don't override all short cuts. + // A Ctrl-1 action is no default Qt key binding and should be triggerable. + action->setShortcut(Qt::CTRL + Qt::Key_1); + QTest::keyClick(view.windowHandle(), Qt::Key_1, Qt::ControlModifier); + QTRY_VERIFY(actionTriggered); + QCOMPARE(inputFieldValue(), QString("yx")); + + // Remove focus from the input field. A QKeySequence::Copy action still must not be triggered. + evaluateJavaScriptSync(view.page(), "input1.blur();"); + action->setShortcut(QKeySequence::Copy); + actionTriggered = false; + QTest::keyClick(view.windowHandle(), Qt::Key_C, Qt::ControlModifier); + // Add some text in the input field to ensure that the key event went through. + evaluateJavaScriptSync(view.page(), "input1.focus();"); + QTest::keyClick(view.windowHandle(), Qt::Key_U); + QTRY_COMPARE(inputFieldValue(), QString("yux")); + QVERIFY(!actionTriggered); +} + class TestInputContext : public QPlatformInputContext { public: -- cgit v1.2.3