Commit 3902b27e authored by Joerg Bornemann's avatar Joerg Bornemann
Browse files

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: default avatarQt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: default avatarAlexandru Croitor <alexandru.croitor@qt.io>
Showing with 198 additions and 5 deletions
...@@ -79,6 +79,7 @@ SOURCES = \ ...@@ -79,6 +79,7 @@ SOURCES = \
qrc_protocol_handler_qt.cpp \ qrc_protocol_handler_qt.cpp \
render_view_observer_host_qt.cpp \ render_view_observer_host_qt.cpp \
render_widget_host_view_qt.cpp \ render_widget_host_view_qt.cpp \
render_widget_host_view_qt_delegate.cpp \
renderer/content_renderer_client_qt.cpp \ renderer/content_renderer_client_qt.cpp \
renderer/render_frame_observer_qt.cpp \ renderer/render_frame_observer_qt.cpp \
renderer/render_view_observer_qt.cpp \ renderer/render_view_observer_qt.cpp \
......
/****************************************************************************
**
** 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 <QtCore/qvariant.h>
#include <QtGui/qevent.h>
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
#include <QtGui/private/qinputcontrol_p.h>
#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
...@@ -48,6 +48,7 @@ ...@@ -48,6 +48,7 @@
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QCursor; class QCursor;
class QEvent; class QEvent;
class QKeyEvent;
class QPainter; class QPainter;
class QSGLayer; class QSGLayer;
class QSGNode; class QSGNode;
...@@ -85,6 +86,7 @@ public: ...@@ -85,6 +86,7 @@ public:
virtual void windowChanged() = 0; virtual void windowChanged() = 0;
virtual bool forwardEvent(QEvent *) = 0; virtual bool forwardEvent(QEvent *) = 0;
virtual QVariant inputMethodQuery(Qt::InputMethodQuery query) = 0; virtual QVariant inputMethodQuery(Qt::InputMethodQuery query) = 0;
virtual bool handleShortcutOverrideEvent(QKeyEvent *event);
}; };
class QWEBENGINE_EXPORT RenderWidgetHostViewQtDelegate { class QWEBENGINE_EXPORT RenderWidgetHostViewQtDelegate {
......
...@@ -241,10 +241,12 @@ void RenderWidgetHostViewQtDelegateQuick::inputMethodStateChanged(bool editorVis ...@@ -241,10 +241,12 @@ void RenderWidgetHostViewQtDelegateQuick::inputMethodStateChanged(bool editorVis
bool RenderWidgetHostViewQtDelegateQuick::event(QEvent *event) bool RenderWidgetHostViewQtDelegateQuick::event(QEvent *event)
{ {
if (event->type() == QEvent::ShortcutOverride) { if (event->type() == QEvent::ShortcutOverride) {
if (editorActionForKeyEvent(static_cast<QKeyEvent*>(event)) != QQuickWebEngineView::NoWebAction) { QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
event->accept(); if (m_client->handleShortcutOverrideEvent(keyEvent))
return true; return true;
} if (editorActionForKeyEvent(keyEvent) != QQuickWebEngineView::NoWebAction)
event->accept();
return true;
} }
if (event->type() == QEvent::NativeGesture) if (event->type() == QEvent::NativeGesture)
......
...@@ -59,6 +59,15 @@ ...@@ -59,6 +59,15 @@
namespace QtWebEngineCore { 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 { class RenderWidgetHostViewQuickItem : public QQuickItem {
public: public:
RenderWidgetHostViewQuickItem(RenderWidgetHostViewQtDelegateClient *client) : m_client(client) RenderWidgetHostViewQuickItem(RenderWidgetHostViewQtDelegateClient *client) : m_client(client)
...@@ -68,6 +77,14 @@ public: ...@@ -68,6 +77,14 @@ public:
setFocus(true); setFocus(true);
} }
protected: protected:
bool event(QEvent *event) override
{
if (event->type() == QEvent::ShortcutOverride) {
handleShortcutOverrideEvent(m_client, static_cast<QKeyEvent *>(event));
return true;
}
return QQuickItem::event(event);
}
void focusInEvent(QFocusEvent *event) override void focusInEvent(QFocusEvent *event) override
{ {
m_client->forwardEvent(event); m_client->forwardEvent(event);
...@@ -437,8 +454,8 @@ bool RenderWidgetHostViewQtDelegateWidget::event(QEvent *event) ...@@ -437,8 +454,8 @@ bool RenderWidgetHostViewQtDelegateWidget::event(QEvent *event)
// We forward focus events later, once they have made it to the m_rootItem. // We forward focus events later, once they have made it to the m_rootItem.
return QQuickWidget::event(event); return QQuickWidget::event(event);
case QEvent::ShortcutOverride: case QEvent::ShortcutOverride:
if (editorActionForKeyEvent(static_cast<QKeyEvent*>(event)) != QWebEnginePage::NoWebAction) { if (event->type() == QEvent::ShortcutOverride) {
event->accept(); handleShortcutOverrideEvent(m_client, static_cast<QKeyEvent *>(event));
return true; return true;
} }
break; break;
......
...@@ -42,6 +42,7 @@ ...@@ -42,6 +42,7 @@
#include <QTcpServer> #include <QTcpServer>
#include <QTcpSocket> #include <QTcpSocket>
#include <QStyle> #include <QStyle>
#include <QtWidgets/qaction.h>
#define VERIFY_INPUTMETHOD_HINTS(actual, expect) \ #define VERIFY_INPUTMETHOD_HINTS(actual, expect) \
QVERIFY(actual == expect); QVERIFY(actual == expect);
...@@ -95,6 +96,7 @@ private Q_SLOTS: ...@@ -95,6 +96,7 @@ private Q_SLOTS:
void keyboardEvents(); void keyboardEvents();
void keyboardFocusAfterPopup(); void keyboardFocusAfterPopup();
void postData(); void postData();
void inputFieldOverridesShortcuts();
void softwareInputPanel(); void softwareInputPanel();
void inputMethods(); void inputMethods();
...@@ -1302,6 +1304,62 @@ void tst_QWebEngineView::postData() ...@@ -1302,6 +1304,62 @@ void tst_QWebEngineView::postData()
server.close(); 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("<html><body onload=\"input1=document.getElementById('input1')\">"
"<input id=\"dummy\" type=\"text\">"
"<input id=\"input1\" type=\"text\" value=\"x\">"
"</body></html>"));
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 class TestInputContext : public QPlatformInputContext
{ {
public: public:
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment