From faec509a21b4700dbf271c4dcbb3a993f1c4042f Mon Sep 17 00:00:00 2001 From: Jocelyn Turcotte Date: Thu, 29 Jan 2015 12:06:28 +0100 Subject: Propagate unhandled key events to the QtWebEngine view's parent This allows applications to receive unhandled key events from the page by setting an event handler on the view's parent widget/item, like it was possible with QtWebKit. This is different in that events first have to asynchronously go through the QtWebEngineProcess. If the WebEngine view has the keyboard focus, the events will be consumed inconditionally by the RenderWidgetHostViewQtDelegates, and a copy will be resent to the view's parent if it wasn't consumed. This sends it to the parent instead of the QWebEngineView directly since those are only unhandled events, unlike with other widgets where you can first intercept events. It is done that way also in cases where the QWebEngineView would be be the focus widget directly in the future, instead of the RWHV. If applications want to intercept key events before they reach the page, they need to use an event filter on the QWebEngineView's children or globally on the application. Change-Id: I3b48f5212d3f238a1c0497cec1db6ae3badbad26 Reviewed-by: Andras Becsi --- src/core/core_gyp_generator.pro | 1 + src/core/native_web_keyboard_event_qt.cpp | 97 ++++++++++++++++++++++ src/core/qtwebengine_extras.gypi | 3 + src/core/render_widget_host_view_qt.cpp | 9 +- src/core/web_contents_adapter_client.h | 2 + src/core/web_contents_delegate_qt.cpp | 7 ++ src/core/web_contents_delegate_qt.h | 18 ++-- src/core/web_event_factory.cpp | 2 +- src/webengine/api/qquickwebengineview.cpp | 7 ++ src/webengine/api/qquickwebengineview_p_p.h | 1 + src/webenginewidgets/api/qwebenginepage.cpp | 6 ++ src/webenginewidgets/api/qwebenginepage_p.h | 1 + .../data/tst_unhandledKeyEventPropagation.qml | 95 +++++++++++++++++++++ .../widgets/qwebengineview/tst_qwebengineview.cpp | 44 ++++++++++ 14 files changed, 284 insertions(+), 9 deletions(-) create mode 100644 src/core/native_web_keyboard_event_qt.cpp create mode 100644 tests/auto/quick/qmltests/data/tst_unhandledKeyEventPropagation.qml diff --git a/src/core/core_gyp_generator.pro b/src/core/core_gyp_generator.pro index ae2422957..2ec2816c1 100644 --- a/src/core/core_gyp_generator.pro +++ b/src/core/core_gyp_generator.pro @@ -54,6 +54,7 @@ SOURCES = \ javascript_dialog_controller.cpp \ javascript_dialog_manager_qt.cpp \ media_capture_devices_dispatcher.cpp \ + native_web_keyboard_event_qt.cpp \ network_delegate_qt.cpp \ ozone_platform_eglfs.cpp \ process_main.cpp \ diff --git a/src/core/native_web_keyboard_event_qt.cpp b/src/core/native_web_keyboard_event_qt.cpp new file mode 100644 index 000000000..072b4dd9d --- /dev/null +++ b/src/core/native_web_keyboard_event_qt.cpp @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/public/browser/native_web_keyboard_event.h" +#include + +namespace { + +// We need to copy |os_event| in NativeWebKeyboardEvent because it is +// queued in RenderWidgetHost and may be passed and used +// RenderViewHostDelegate::HandledKeybardEvent after the original aura +// event is destroyed. +gfx::NativeEvent CopyEvent(gfx::NativeEvent event) +{ + return event ? reinterpret_cast(new QKeyEvent(*reinterpret_cast(event))) : 0; +} + +void DestroyEvent(gfx::NativeEvent event) +{ + delete reinterpret_cast(event); +} + +} // namespace + +using blink::WebKeyboardEvent; + +namespace content { + +NativeWebKeyboardEvent::NativeWebKeyboardEvent() + : os_event(0), + skip_in_browser(false) +{ +} + +NativeWebKeyboardEvent::NativeWebKeyboardEvent(gfx::NativeEvent native_event) + : os_event(CopyEvent(native_event)), + skip_in_browser(false) +{ +} + +NativeWebKeyboardEvent::NativeWebKeyboardEvent(const NativeWebKeyboardEvent& other) + : WebKeyboardEvent(other), + os_event(CopyEvent(other.os_event)), + skip_in_browser(other.skip_in_browser) +{ +} + +NativeWebKeyboardEvent& NativeWebKeyboardEvent::operator=(const NativeWebKeyboardEvent& other) { + WebKeyboardEvent::operator=(other); + DestroyEvent(os_event); + os_event = CopyEvent(other.os_event); + skip_in_browser = other.skip_in_browser; + return *this; +} + +NativeWebKeyboardEvent::~NativeWebKeyboardEvent() { + DestroyEvent(os_event); +} + +} // namespace content diff --git a/src/core/qtwebengine_extras.gypi b/src/core/qtwebengine_extras.gypi index 0592df73b..b0e9bc697 100644 --- a/src/core/qtwebengine_extras.gypi +++ b/src/core/qtwebengine_extras.gypi @@ -27,6 +27,9 @@ ['exclude', 'browser/web_contents/web_contents_view_mac\\.(mm|h)$'], ['exclude', 'browser/web_contents/web_contents_view_win\\.(cc|h)$'], ['exclude', 'browser/renderer_host/gtk_im_context_wrapper\\.cc$'], + ['exclude', 'browser/renderer_host/native_web_keyboard_event_android.cc$'], + ['exclude', 'browser/renderer_host/native_web_keyboard_event_aura.cc$'], + ['exclude', 'browser/renderer_host/native_web_keyboard_event_mac.mm$'], ['exclude', 'browser/renderer_host/pepper/pepper_truetype_font_list_pango\\.cc$'], ['exclude', 'browser/renderer_host/render_widget_host_view_android\\.(cc|h)$'], ['exclude', 'browser/renderer_host/render_widget_host_view_aura\\.(cc|h)$'], diff --git a/src/core/render_widget_host_view_qt.cpp b/src/core/render_widget_host_view_qt.cpp index bf4d5bf37..3741d8876 100644 --- a/src/core/render_widget_host_view_qt.cpp +++ b/src/core/render_widget_host_view_qt.cpp @@ -880,10 +880,17 @@ void RenderWidgetHostViewQt::handleKeyEvent(QKeyEvent *ev) UnlockMouse(); content::NativeWebKeyboardEvent webEvent = WebEventFactory::toWebKeyboardEvent(ev); - m_host->ForwardKeyboardEvent(webEvent); if (webEvent.type == blink::WebInputEvent::RawKeyDown && !ev->text().isEmpty()) { + // Blink won't consume the RawKeyDown, but rather the Char event in this case. + // Make sure to skip the former on the way back. The same os_event will be set on both of them. + webEvent.skip_in_browser = true; + m_host->ForwardKeyboardEvent(webEvent); + + webEvent.skip_in_browser = false; webEvent.type = blink::WebInputEvent::Char; m_host->ForwardKeyboardEvent(webEvent); + } else { + m_host->ForwardKeyboardEvent(webEvent); } } diff --git a/src/core/web_contents_adapter_client.h b/src/core/web_contents_adapter_client.h index b6d1ef5bb..26d7a03ea 100644 --- a/src/core/web_contents_adapter_client.h +++ b/src/core/web_contents_adapter_client.h @@ -46,6 +46,7 @@ #include #include +QT_FORWARD_DECLARE_CLASS(QKeyEvent) QT_FORWARD_DECLARE_CLASS(QVariant) QT_FORWARD_DECLARE_CLASS(CertificateErrorController) @@ -154,6 +155,7 @@ public: virtual void loadVisuallyCommitted() = 0; virtual void loadFinished(bool success, const QUrl &url, int errorCode = 0, const QString &errorDescription = QString()) = 0; virtual void focusContainer() = 0; + virtual void unhandledKeyEvent(QKeyEvent *event) = 0; virtual void adoptNewWindow(WebContentsAdapter *newWebContents, WindowOpenDisposition disposition, bool userGesture, const QRect & initialGeometry) = 0; virtual void close() = 0; virtual bool contextMenuRequested(const WebEngineContextMenuData&) = 0; diff --git a/src/core/web_contents_delegate_qt.cpp b/src/core/web_contents_delegate_qt.cpp index d24b8a2cd..50aafd55e 100644 --- a/src/core/web_contents_delegate_qt.cpp +++ b/src/core/web_contents_delegate_qt.cpp @@ -131,6 +131,13 @@ void WebContentsDelegateQt::LoadProgressChanged(content::WebContents* source, do m_viewClient->loadProgressChanged(qRound(progress * 100)); } +void WebContentsDelegateQt::HandleKeyboardEvent(content::WebContents *, const content::NativeWebKeyboardEvent &event) +{ + Q_ASSERT(!event.skip_in_browser); + if (event.os_event) + m_viewClient->unhandledKeyEvent(reinterpret_cast(event.os_event)); +} + void WebContentsDelegateQt::DidStartProvisionalLoadForFrame(content::RenderFrameHost* render_frame_host, const GURL& validated_url, bool is_error_page, bool is_iframe_srcdoc) { if (is_error_page) { diff --git a/src/core/web_contents_delegate_qt.h b/src/core/web_contents_delegate_qt.h index cb006106e..f3b231798 100644 --- a/src/core/web_contents_delegate_qt.h +++ b/src/core/web_contents_delegate_qt.h @@ -68,17 +68,13 @@ public: void setLastSearchedString(const QString &s) { m_lastSearchedString = s; } int lastReceivedFindReply() const { return m_lastReceivedFindReply; } + // WebContentsDelegate overrides virtual content::WebContents *OpenURLFromTab(content::WebContents *source, const content::OpenURLParams ¶ms) Q_DECL_OVERRIDE; virtual void NavigationStateChanged(const content::WebContents* source, content::InvalidateTypes changed_flags) Q_DECL_OVERRIDE; virtual void AddNewContents(content::WebContents* source, content::WebContents* new_contents, WindowOpenDisposition disposition, const gfx::Rect& initial_pos, bool user_gesture, bool* was_blocked) Q_DECL_OVERRIDE; virtual void CloseContents(content::WebContents *source) Q_DECL_OVERRIDE; virtual void LoadProgressChanged(content::WebContents* source, double progress) Q_DECL_OVERRIDE; - virtual void DidStartProvisionalLoadForFrame(content::RenderFrameHost* render_frame_host, const GURL& validated_url, bool is_error_page, bool is_iframe_srcdoc) Q_DECL_OVERRIDE; - virtual void DidCommitProvisionalLoadForFrame(content::RenderFrameHost* render_frame_host, const GURL& url, ui::PageTransition transition_type) Q_DECL_OVERRIDE; - virtual void DidFailProvisionalLoad(content::RenderFrameHost* render_frame_host, const GURL& validated_url, int error_code, const base::string16& error_description) Q_DECL_OVERRIDE; - virtual void DidFailLoad(content::RenderFrameHost* render_frame_host, const GURL& validated_url, int error_code, const base::string16& error_description) Q_DECL_OVERRIDE; - virtual void DidFinishLoad(content::RenderFrameHost* render_frame_host, const GURL& validated_url) Q_DECL_OVERRIDE; - virtual void DidUpdateFaviconURL(const std::vector& candidates) Q_DECL_OVERRIDE; + virtual void HandleKeyboardEvent(content::WebContents *source, const content::NativeWebKeyboardEvent &event) Q_DECL_OVERRIDE; virtual content::JavaScriptDialogManager *GetJavaScriptDialogManager() Q_DECL_OVERRIDE; virtual void ToggleFullscreenModeForTab(content::WebContents* web_contents, bool enter_fullscreen) Q_DECL_OVERRIDE; virtual bool IsFullscreenForTabOrPending(const content::WebContents* web_contents) const Q_DECL_OVERRIDE; @@ -87,9 +83,17 @@ public: virtual void FindReply(content::WebContents *source, int request_id, int number_of_matches, const gfx::Rect& selection_rect, int active_match_ordinal, bool final_update) Q_DECL_OVERRIDE; virtual void RequestMediaAccessPermission(content::WebContents* web_contents, const content::MediaStreamRequest& request, const content::MediaResponseCallback& callback) Q_DECL_OVERRIDE; virtual void UpdateTargetURL(content::WebContents* source, const GURL& url) Q_DECL_OVERRIDE; - virtual void DidNavigateAnyFrame(content::RenderFrameHost* render_frame_host, const content::LoadCommittedDetails& details, const content::FrameNavigateParams& params) Q_DECL_OVERRIDE; virtual void RequestToLockMouse(content::WebContents *web_contents, bool user_gesture, bool last_unlocked_by_target) Q_DECL_OVERRIDE; + // WebContentsObserver overrides + virtual void DidStartProvisionalLoadForFrame(content::RenderFrameHost *render_frame_host, const GURL &validated_url, bool is_error_page, bool is_iframe_srcdoc) Q_DECL_OVERRIDE; + virtual void DidCommitProvisionalLoadForFrame(content::RenderFrameHost *render_frame_host, const GURL &url, ui::PageTransition transition_type) Q_DECL_OVERRIDE; + virtual void DidFailProvisionalLoad(content::RenderFrameHost *render_frame_host, const GURL &validated_url, int error_code, const base::string16 &error_description) Q_DECL_OVERRIDE; + virtual void DidFailLoad(content::RenderFrameHost *render_frame_host, const GURL &validated_url, int error_code, const base::string16 &error_description) Q_DECL_OVERRIDE; + virtual void DidFinishLoad(content::RenderFrameHost *render_frame_host, const GURL &validated_url) Q_DECL_OVERRIDE; + virtual void DidUpdateFaviconURL(const std::vector &candidates) Q_DECL_OVERRIDE; + virtual void DidNavigateAnyFrame(content::RenderFrameHost *render_frame_host, const content::LoadCommittedDetails &details, const content::FrameNavigateParams ¶ms) Q_DECL_OVERRIDE; + void overrideWebPreferences(content::WebContents *, content::WebPreferences*); void allowCertificateError(const QSharedPointer &) ; void requestGeolocationPermission(const GURL &requestingFrameOrigin, const base::Callback &resultCallback); diff --git a/src/core/web_event_factory.cpp b/src/core/web_event_factory.cpp index 6f9422c8e..eacb59011 100644 --- a/src/core/web_event_factory.cpp +++ b/src/core/web_event_factory.cpp @@ -661,7 +661,7 @@ blink::WebMouseWheelEvent WebEventFactory::toWebWheelEvent(QWheelEvent *ev, doub content::NativeWebKeyboardEvent WebEventFactory::toWebKeyboardEvent(QKeyEvent *ev) { - content::NativeWebKeyboardEvent webKitEvent; + content::NativeWebKeyboardEvent webKitEvent(reinterpret_cast(ev)); webKitEvent.timeStampSeconds = currentTimeForEvent(ev); webKitEvent.modifiers = modifiersForEvent(ev); webKitEvent.type = webEventTypeForEvent(ev); diff --git a/src/webengine/api/qquickwebengineview.cpp b/src/webengine/api/qquickwebengineview.cpp index f6dbc18ff..02d30af61 100644 --- a/src/webengine/api/qquickwebengineview.cpp +++ b/src/webengine/api/qquickwebengineview.cpp @@ -354,6 +354,13 @@ void QQuickWebEngineViewPrivate::focusContainer() q->forceActiveFocus(); } +void QQuickWebEngineViewPrivate::unhandledKeyEvent(QKeyEvent *event) +{ + Q_Q(QQuickWebEngineView); + if (q->parentItem()) + q->window()->sendEvent(q->parentItem(), event); +} + void QQuickWebEngineViewPrivate::adoptNewWindow(WebContentsAdapter *newWebContents, WindowOpenDisposition disposition, bool userGesture, const QRect &) { Q_Q(QQuickWebEngineView); diff --git a/src/webengine/api/qquickwebengineview_p_p.h b/src/webengine/api/qquickwebengineview_p_p.h index 1c1334ee6..28c8ff44e 100644 --- a/src/webengine/api/qquickwebengineview_p_p.h +++ b/src/webengine/api/qquickwebengineview_p_p.h @@ -155,6 +155,7 @@ public: virtual void loadVisuallyCommitted() Q_DECL_OVERRIDE; virtual void loadFinished(bool success, const QUrl &url, int errorCode = 0, const QString &errorDescription = QString()) Q_DECL_OVERRIDE; virtual void focusContainer() Q_DECL_OVERRIDE; + virtual void unhandledKeyEvent(QKeyEvent *event) Q_DECL_OVERRIDE; virtual void adoptNewWindow(WebContentsAdapter *newWebContents, WindowOpenDisposition disposition, bool userGesture, const QRect &) Q_DECL_OVERRIDE; virtual void close() Q_DECL_OVERRIDE; virtual void requestFullScreen(bool) Q_DECL_OVERRIDE; diff --git a/src/webenginewidgets/api/qwebenginepage.cpp b/src/webenginewidgets/api/qwebenginepage.cpp index 32c467c08..aceac91e3 100644 --- a/src/webenginewidgets/api/qwebenginepage.cpp +++ b/src/webenginewidgets/api/qwebenginepage.cpp @@ -271,6 +271,12 @@ void QWebEnginePagePrivate::focusContainer() view->setFocus(); } +void QWebEnginePagePrivate::unhandledKeyEvent(QKeyEvent *event) +{ + if (view && view->parentWidget()) + QGuiApplication::sendEvent(view->parentWidget(), event); +} + void QWebEnginePagePrivate::adoptNewWindow(WebContentsAdapter *newWebContents, WindowOpenDisposition disposition, bool userGesture, const QRect &initialGeometry) { Q_Q(QWebEnginePage); diff --git a/src/webenginewidgets/api/qwebenginepage_p.h b/src/webenginewidgets/api/qwebenginepage_p.h index 93b94bfa5..6c6520414 100644 --- a/src/webenginewidgets/api/qwebenginepage_p.h +++ b/src/webenginewidgets/api/qwebenginepage_p.h @@ -120,6 +120,7 @@ public: virtual void loadVisuallyCommitted() Q_DECL_OVERRIDE { } virtual void loadFinished(bool success, const QUrl &url, int errorCode = 0, const QString &errorDescription = QString()) Q_DECL_OVERRIDE; virtual void focusContainer() Q_DECL_OVERRIDE; + virtual void unhandledKeyEvent(QKeyEvent *event) Q_DECL_OVERRIDE; virtual void adoptNewWindow(WebContentsAdapter *newWebContents, WindowOpenDisposition disposition, bool userGesture, const QRect &initialGeometry) Q_DECL_OVERRIDE; virtual void close() Q_DECL_OVERRIDE; virtual bool contextMenuRequested(const WebEngineContextMenuData &data) Q_DECL_OVERRIDE; diff --git a/tests/auto/quick/qmltests/data/tst_unhandledKeyEventPropagation.qml b/tests/auto/quick/qmltests/data/tst_unhandledKeyEventPropagation.qml new file mode 100644 index 000000000..8d5fd5375 --- /dev/null +++ b/tests/auto/quick/qmltests/data/tst_unhandledKeyEventPropagation.qml @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtWebEngine 1.0 + +Item { + id: parentItem + width: 400 + height: 300 + + property var pressEvents: [] + property var releaseEvents: [] + Keys.onPressed: pressEvents.push(event.key) + Keys.onReleased: releaseEvents.push(event.key) + + TestWebEngineView { + id: webEngineView + anchors.fill: parent + focus: true + } + TestCase { + name: "WebEngineViewUnhandledKeyEventPropagation" + + when: false + Timer { + running: parent.windowShown + repeat: false + interval: 1 + onTriggered: parent.when = true + } + + function test_keyboardModifierMapping() { + webEngineView.loadHtml("") + webEngineView.waitForLoadSucceeded() + webEngineView.runJavaScript("document.body.firstChild.focus()") + + keyPress(Qt.Key_A) + keyRelease(Qt.Key_A) + keyPress(Qt.Key_Left) + keyRelease(Qt.Key_Left) + keyPress(Qt.Key_Left) + keyRelease(Qt.Key_Left) + + for (var i = 0; i < 20 && parentItem.releaseEvents.length < 3; i++) + wait(100) + + compare(parentItem.pressEvents.length, 1) + compare(parentItem.pressEvents[0], Qt.Key_Left) + compare(parentItem.releaseEvents.length, 3) + compare(parentItem.releaseEvents[0], Qt.Key_A) + compare(parentItem.releaseEvents[1], Qt.Key_Left) + compare(parentItem.releaseEvents[2], Qt.Key_Left) + } + } +} diff --git a/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp b/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp index 3893397cd..83f65f9d0 100644 --- a/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp +++ b/tests/auto/widgets/qwebengineview/tst_qwebengineview.cpp @@ -50,6 +50,7 @@ private Q_SLOTS: void reusePage(); void microFocusCoordinates(); void focusInputTypes(); + void unhandledKeyEventPropagation(); void horizontalScrollbarTest(); void crashTests(); @@ -321,6 +322,49 @@ void tst_QWebEngineView::focusInputTypes() #endif } +class KeyEventRecordingWidget : public QWidget { +public: + QList pressEvents; + QList releaseEvents; + void keyPressEvent(QKeyEvent *e) Q_DECL_OVERRIDE { pressEvents << *e; } + void keyReleaseEvent(QKeyEvent *e) Q_DECL_OVERRIDE { releaseEvents << *e; } +}; + +void tst_QWebEngineView::unhandledKeyEventPropagation() +{ + KeyEventRecordingWidget parentWidget; + QWebEngineView webView(&parentWidget); + parentWidget.show(); + QTest::qWaitForWindowExposed(&webView); + + QSignalSpy loadSpy(&webView, SIGNAL(loadFinished(bool))); + webView.setHtml(""); + QTRY_COMPARE(loadSpy.count(), 1); + + evaluateJavaScriptSync(webView.page(), "document.body.firstChild.focus()"); + + QTest::sendKeyEvent(QTest::Press, parentWidget.windowHandle(), Qt::Key_A, 'a', Qt::NoModifier); + QTest::sendKeyEvent(QTest::Release, parentWidget.windowHandle(), Qt::Key_A, 'a', Qt::NoModifier); + QTest::sendKeyEvent(QTest::Press, parentWidget.windowHandle(), Qt::Key_Left, QString(), Qt::NoModifier); + QTest::sendKeyEvent(QTest::Release, parentWidget.windowHandle(), Qt::Key_Left, QString(), Qt::NoModifier); + QTest::sendKeyEvent(QTest::Press, parentWidget.windowHandle(), Qt::Key_Left, QString(), Qt::NoModifier); + QTest::sendKeyEvent(QTest::Release, parentWidget.windowHandle(), Qt::Key_Left, QString(), Qt::NoModifier); + + // All this happens asychronously, wait for the last release event to know when we're done. + for (int i = 0; i < 20 && parentWidget.releaseEvents.size() < 3; ++i) + QTest::qWait(100); + + // The page will consume the 'a' and the first left key presses, the second left won't be + // used since the cursor will already be at the left end of the text input. + // Key releases will all come back unconsumed. + QCOMPARE(parentWidget.pressEvents.size(), 1); + QCOMPARE(parentWidget.pressEvents[0].key(), (int)Qt::Key_Left); + QCOMPARE(parentWidget.releaseEvents.size(), 3); + QCOMPARE(parentWidget.releaseEvents[0].key(), (int)Qt::Key_A); + QCOMPARE(parentWidget.releaseEvents[1].key(), (int)Qt::Key_Left); + QCOMPARE(parentWidget.releaseEvents[2].key(), (int)Qt::Key_Left); +} + void tst_QWebEngineView::horizontalScrollbarTest() { #if !defined(QWEBENGINEPAGE_SCROLL) -- cgit v1.2.3