diff options
-rw-r--r-- | src/core/extensions/extension_web_contents_observer_qt.cpp | 13 | ||||
-rw-r--r-- | src/core/render_widget_host_view_qt.cpp | 54 | ||||
-rw-r--r-- | src/core/render_widget_host_view_qt.h | 14 | ||||
-rw-r--r-- | src/core/web_contents_delegate_qt.cpp | 4 | ||||
-rw-r--r-- | tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp | 108 |
5 files changed, 154 insertions, 39 deletions
diff --git a/src/core/extensions/extension_web_contents_observer_qt.cpp b/src/core/extensions/extension_web_contents_observer_qt.cpp index 22092be30..a33954a20 100644 --- a/src/core/extensions/extension_web_contents_observer_qt.cpp +++ b/src/core/extensions/extension_web_contents_observer_qt.cpp @@ -41,17 +41,8 @@ void ExtensionWebContentsObserverQt::CreateForWebContents(content::WebContents * void ExtensionWebContentsObserverQt::RenderFrameCreated(content::RenderFrameHost *render_frame_host) { ExtensionWebContentsObserver::RenderFrameCreated(render_frame_host); - - if (web_contents()->IsInnerWebContentsForGuest() && static_cast<content::RenderFrameHostImpl *>(render_frame_host)->is_local_root_subframe()) { - content::WebContents *parent = web_contents()->GetOutermostWebContents(); - QtWebEngineCore::RenderWidgetHostViewQt *main_rwhv = static_cast<QtWebEngineCore::RenderWidgetHostViewQt *>(parent->GetRenderWidgetHostView()); - // Main frame of guest WebContents - content::RenderWidgetHost *guest_render_widget_host = web_contents()->GetRenderViewHost()->GetWidget(); - main_rwhv->addGuest(guest_render_widget_host); - // The frame which holds the actual PDF content inside the guest - content::RenderWidgetHost *pdf_render_widget_host = render_frame_host->GetRenderWidgetHost(); - main_rwhv->addGuest(pdf_render_widget_host); - } + QtWebEngineCore::RenderWidgetHostViewQt::registerInputEventObserver(web_contents(), + render_frame_host); const Extension *extension = GetExtensionFromFrame(render_frame_host, false); if (!extension) diff --git a/src/core/render_widget_host_view_qt.cpp b/src/core/render_widget_host_view_qt.cpp index 19b9f462c..3d4e5bbc2 100644 --- a/src/core/render_widget_host_view_qt.cpp +++ b/src/core/render_widget_host_view_qt.cpp @@ -18,6 +18,7 @@ #include "components/viz/common/surfaces/frame_sink_id_allocator.h" #include "components/viz/host/host_frame_sink_manager.h" #include "content/browser/compositor/image_transport_factory.h" +#include "content/browser/renderer_host/render_frame_host_impl.h" #include "content/browser/renderer_host/frame_tree.h" #include "content/browser/renderer_host/frame_tree_node.h" #include "content/browser/renderer_host/cursor_manager.h" @@ -29,6 +30,7 @@ #include "content/browser/renderer_host/ui_events_helper.h" #include "content/common/content_switches_internal.h" #include "content/common/cursors/webcursor.h" +#include "content/public/browser/web_contents.h" #include "third_party/skia/include/core/SkColor.h" #include "ui/base/cursor/cursor.h" #include "ui/base/resource/resource_bundle.h" @@ -141,33 +143,10 @@ public: } }; -class GuestInputEventObserverQt : public content::RenderWidgetHost::InputEventObserver -{ -public: - GuestInputEventObserverQt(RenderWidgetHostViewQt *rwhv) - : m_rwhv(rwhv) - { - } - ~GuestInputEventObserverQt() {} - - void OnInputEvent(const blink::WebInputEvent&) override {} - void OnInputEventAck(blink::mojom::InputEventResultSource, - blink::mojom::InputEventResultState state, - const blink::WebInputEvent &event) override - { - if (event.GetType() == blink::WebInputEvent::Type::kMouseWheel) - m_rwhv->WheelEventAck(static_cast<const blink::WebMouseWheelEvent &>(event), state); - } - -private: - RenderWidgetHostViewQt *m_rwhv; -}; - RenderWidgetHostViewQt::RenderWidgetHostViewQt(content::RenderWidgetHost *widget) : content::RenderWidgetHostViewBase::RenderWidgetHostViewBase(widget) , m_taskRunner(base::ThreadTaskRunnerHandle::Get()) , m_gestureProvider(QtGestureProviderConfig(), this) - , m_guestInputEventObserver(new GuestInputEventObserverQt(this)) , m_frameSinkId(host()->GetFrameSinkId()) , m_delegateClient(new RenderWidgetHostViewQtDelegateClient(this)) { @@ -247,9 +226,34 @@ void RenderWidgetHostViewQt::setAdapterClient(WebContentsAdapterClient *adapterC m_adapterClient = nullptr; }); } -void RenderWidgetHostViewQt::addGuest(content::RenderWidgetHost *rwh) +void RenderWidgetHostViewQt::OnInputEventAck(blink::mojom::InputEventResultSource, + blink::mojom::InputEventResultState state, + const blink::WebInputEvent &event) { - rwh->AddInputEventObserver(m_guestInputEventObserver.get()); + if (event.GetType() == blink::WebInputEvent::Type::kMouseWheel) + WheelEventAck(static_cast<const blink::WebMouseWheelEvent &>(event), state); +} + +// static +// Called when new child/guest renderframes created. +void RenderWidgetHostViewQt::registerInputEventObserver(content::WebContents *webContents, + content::RenderFrameHost *rfh) +{ + if (static_cast<content::RenderFrameHostImpl *>(rfh)->is_local_root_subframe()) { + content::WebContents *parent = webContents->GetOutermostWebContents(); + QtWebEngineCore::RenderWidgetHostViewQt *mainRwhv = + static_cast<QtWebEngineCore::RenderWidgetHostViewQt *>( + parent->GetRenderWidgetHostView()); + // Child (originAgentCluster) or guest (pdf) frame that is embedded into the main frame + content::RenderWidgetHost *childFrame = rfh->GetRenderWidgetHost(); + childFrame->AddInputEventObserver(mainRwhv); + + if (webContents->IsInnerWebContentsForGuest()) { + // The frame which holds the actual PDF content inside the guest + content::RenderWidgetHost *guestFrame = webContents->GetRenderViewHost()->GetWidget(); + guestFrame->AddInputEventObserver(mainRwhv); + } + } } void RenderWidgetHostViewQt::InitAsChild(gfx::NativeView) diff --git a/src/core/render_widget_host_view_qt.h b/src/core/render_widget_host_view_qt.h index aebf59ddb..25fd20115 100644 --- a/src/core/render_widget_host_view_qt.h +++ b/src/core/render_widget_host_view_qt.h @@ -21,6 +21,7 @@ namespace content { class RenderFrameHost; class RenderWidgetHostImpl; +class WebContents; } namespace ui { @@ -30,7 +31,7 @@ class TouchSelectionController; namespace QtWebEngineCore { class RenderWidgetHostViewQtDelegateClient; -class GuestInputEventObserverQt; +class InputEventObserverQt; class TouchSelectionControllerClientQt; class WebContentsAccessibilityQt; class WebContentsAdapterClient; @@ -41,6 +42,7 @@ class RenderWidgetHostViewQt , public base::SupportsWeakPtr<RenderWidgetHostViewQt> , public content::TextInputManager::Observer , public content::RenderFrameMetadataProvider::Observer + , public content::RenderWidgetHost::InputEventObserver { public: RenderWidgetHostViewQt(content::RenderWidgetHost* widget); @@ -51,7 +53,6 @@ public: WebContentsAdapterClient *adapterClient() { return m_adapterClient; } void setAdapterClient(WebContentsAdapterClient *adapterClient); RenderWidgetHostViewQtDelegateClient *delegateClient() const { return m_delegateClient.get(); } - void addGuest(content::RenderWidgetHost *); // Overridden from RenderWidgetHostView: void InitAsChild(gfx::NativeView) override; @@ -149,6 +150,14 @@ public: void OnRenderFrameSubmission() override {} void OnLocalSurfaceIdChanged(const cc::RenderFrameMetadata &) override {} + // Overridden from content::RenderWidgetHost::InputEventObserver + void OnInputEvent(const blink::WebInputEvent &) override { } + void OnInputEventAck(blink::mojom::InputEventResultSource, + blink::mojom::InputEventResultState state, + const blink::WebInputEvent &event) override; + + static void registerInputEventObserver(content::WebContents *, content::RenderFrameHost *); + // Called from RenderWidgetHostViewQtDelegateClient. Compositor::Id compositorId(); void notifyShown(); @@ -185,7 +194,6 @@ private: std::unique_ptr<content::CursorManager> m_cursorManager; ui::FilteredGestureProvider m_gestureProvider; - std::unique_ptr<GuestInputEventObserverQt> m_guestInputEventObserver; viz::FrameSinkId m_frameSinkId; std::unique_ptr<RenderWidgetHostViewQtDelegateClient> m_delegateClient; diff --git a/src/core/web_contents_delegate_qt.cpp b/src/core/web_contents_delegate_qt.cpp index fd445f88a..b58b56500 100644 --- a/src/core/web_contents_delegate_qt.cpp +++ b/src/core/web_contents_delegate_qt.cpp @@ -255,6 +255,10 @@ void WebContentsDelegateQt::RenderFrameCreated(content::RenderFrameHost *render_ { content::FrameTreeNode *node = static_cast<content::RenderFrameHostImpl *>(render_frame_host)->frame_tree_node(); m_frameFocusedObserver.addNode(node); + + // If it's a child frame (render_widget_host_view_child_frame) install an InputEventObserver on + // it. Note that it is only needed for WheelEventAck. + RenderWidgetHostViewQt::registerInputEventObserver(web_contents(), render_frame_host); } void WebContentsDelegateQt::PrimaryMainFrameRenderProcessGone(base::TerminationStatus status) diff --git a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp index 39629bcb8..34646cfed 100644 --- a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp +++ b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp @@ -36,6 +36,7 @@ #include <QPaintEngine> #include <QPushButton> #include <QScreen> +#include <QWheelEvent> #if defined(QT_STATEMACHINE_LIB) # include <QStateMachine> #endif @@ -265,6 +266,7 @@ private Q_SLOTS: void localToRemoteNavigation(); void clientHints(); + void childFrameInput(); private: static QPoint elementCenter(QWebEnginePage *page, const QString &id); @@ -292,6 +294,13 @@ private: QTest::touchEvent(window, s_touchDevice.get()).release(1, p); } }; + + void makeScroll(QWidget *target, QPointF pos, QPoint globalPos, QPoint angleDelta) + { + QWheelEvent ev(pos, globalPos, QPoint(0, 0), angleDelta, Qt::NoButton, Qt::NoModifier, + Qt::NoScrollPhase, false); + QGuiApplication::sendEvent(target, &ev); + } }; tst_QWebEnginePage::tst_QWebEnginePage() @@ -5147,6 +5156,105 @@ void tst_QWebEnginePage::clientHints() } +void tst_QWebEnginePage::childFrameInput() +{ + HttpServer server; + server.setHostDomain("localhost"); + + // The cross-origin policy blocks scripting this frame with QWebEnginePage::runJavaScript. + // Use console messages to validate events. + QString innerHtml( + "<html><head><style>body{height:1200px;width:1200px;}</style></head><body>test<script>" + " let lastX, lastY = 0;" + " document.onscroll = (e) => {" + " if (window.scrollY > lastY) console.log(\"Down\");" + " if (window.scrollY < lastY) console.log(\"Up\");" + " if (window.scrollX > lastX) console.log(\"Right\");" + " if (window.scrollX < lastX) console.log(\"Left\");" + " lastX = window.scrollX;" + " lastY = window.scrollY;" + " };" + " window.onload = () => {console.log('loaded');};" + "</script></body></html>"); + + QVERIFY(server.start()); + connect(&server, &HttpServer::newRequest, [&](HttpReqRep *rr) { + if (rr->requestPath() == "/main.html") { + // the Origin-Agent-Cluster header enables dedicated processes for origins + rr->setResponseHeader("Origin-Agent-Cluster", "?1"); + // the same-site-cross-origin page forces to create the frame in a different process + server.setHostDomain("sub.localhost"); + rr->setResponseBody(("<html><body>" + "<iframe id=\"iframe\" width=90% height=90% src=\"" + + server.url().toString().toUtf8() + + "inner.html\"></iframe>" + "</body></html>")); + } + if (rr->requestPath() == "/inner.html") + rr->setResponseBody(innerHtml.toUtf8()); + rr->sendResponse(); + }); + + QWebEngineView view; + ConsolePage page; + view.setPage(&page); + view.resize(640, 480); + QSignalSpy loadSpy(&page, &QWebEnginePage::loadFinished); + page.load(server.url("/main.html")); + QTRY_COMPARE_WITH_TIMEOUT(loadSpy.size(), 1, 20000); + + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + QTRY_VERIFY(evaluateJavaScriptSync(&page, "window.originAgentCluster").toBool()); + + // make sure the frame is loaded + QTRY_COMPARE(page.messages.size(), 1); + QTRY_COMPARE(page.messages[0], QString("loaded")); + + // focus + evaluateJavaScriptSync(&page, "document.getElementById('iframe').contentWindow.focus()"); + QTRY_COMPARE(evaluateJavaScriptSync(&page, "document.activeElement.id").toString(), + QStringLiteral("iframe")); + + QPoint globalPos = view.windowHandle()->position(); + QPoint p = elementCenter(&page, QString("iframe")); + + // Even if the document is loaded, it is not necessarily drawn. + // Hit-testing (in Viz) for pointer events will be flacky in this scenario. + // Send keyClick events first so the target frame will be cached for wheel events. + QTest::keyClick(view.focusProxy(), Qt::Key_Down); + QTRY_COMPARE(page.messages.size(), 2); + QTRY_COMPARE(page.messages[1], QString("Down")); + + QTest::keyClick(view.focusProxy(), Qt::Key_Up); + QTRY_COMPARE(page.messages.size(), 3); + QTRY_COMPARE(page.messages[2], QString("Up")); + + QTest::keyClick(view.focusProxy(), Qt::Key_Right); + QTRY_COMPARE(page.messages.size(), 4); + QTRY_COMPARE(page.messages[3], QString("Right")); + + QTest::keyClick(view.focusProxy(), Qt::Key_Left); + QTRY_COMPARE(page.messages.size(), 5); + QTRY_COMPARE(page.messages[4], QString("Left")); + + makeScroll(view.focusProxy(), p, globalPos, QPoint(0, -120)); + QTRY_COMPARE(page.messages.size(), 6); + QTRY_COMPARE(page.messages[5], QString("Down")); + + makeScroll(view.focusProxy(), p, globalPos, QPoint(0, 120)); + QTRY_COMPARE(page.messages.size(), 7); + QTRY_COMPARE(page.messages[6], QString("Up")); + + makeScroll(view.focusProxy(), p, globalPos, QPoint(-120, 0)); + QTRY_COMPARE(page.messages.size(), 8); + QTRY_COMPARE(page.messages[7], QString("Right")); + + makeScroll(view.focusProxy(), p, globalPos, QPoint(120, 0)); + QTRY_COMPARE(page.messages.size(), 9); + QTRY_COMPARE(page.messages[8], QString("Left")); +} + static QByteArrayList params = {QByteArrayLiteral("--use-fake-device-for-media-stream")}; W_QTEST_MAIN(tst_QWebEnginePage, params) |