| // Copyright 2018 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/browser/site_per_process_browsertest.h" |
| |
| #include <tuple> |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/feature_list.h" |
| #include "base/json/json_reader.h" |
| #include "base/stl_util.h" |
| #include "base/task/post_task.h" |
| #include "base/test/bind_test_util.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/test_timeouts.h" |
| #include "build/build_config.h" |
| #include "components/viz/common/features.h" |
| #include "components/viz/test/host_frame_sink_manager_test_api.h" |
| #include "content/browser/compositor/surface_utils.h" |
| #include "content/browser/renderer_host/cursor_manager.h" |
| #include "content/browser/renderer_host/input/synthetic_smooth_scroll_gesture.h" |
| #include "content/browser/renderer_host/input/synthetic_tap_gesture.h" |
| #include "content/browser/renderer_host/input/synthetic_touchpad_pinch_gesture.h" |
| #include "content/browser/renderer_host/input/touch_emulator.h" |
| #include "content/browser/renderer_host/render_widget_host_input_event_router.h" |
| #include "content/browser/renderer_host/render_widget_host_view_child_frame.h" |
| #include "content/common/frame_messages.h" |
| #include "content/common/input/input_handler.mojom-test-utils.h" |
| #include "content/common/view_messages.h" |
| #include "content/common/widget_messages.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/screen_info.h" |
| #include "content/public/common/use_zoom_for_dsf_policy.h" |
| #include "content/public/common/web_preferences.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/content_browser_test_utils.h" |
| #include "content/public/test/hit_test_region_observer.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "content/public/test/test_utils.h" |
| #include "content/shell/common/shell_switches.h" |
| #include "content/test/mock_overscroll_observer.h" |
| #include "ui/base/ui_base_features.h" |
| #include "ui/display/display_switches.h" |
| #include "ui/events/base_event_utils.h" |
| #include "ui/events/gesture_detection/gesture_configuration.h" |
| #include "ui/events/gesture_detection/gesture_provider_config_helper.h" |
| #include "ui/events/platform/platform_event_source.h" |
| #include "ui/gfx/geometry/quad_f.h" |
| |
| #if defined(USE_AURA) |
| #include "content/browser/renderer_host/render_widget_host_view_aura.h" |
| #include "content/public/browser/overscroll_configuration.h" |
| #include "content/test/mock_overscroll_controller_delegate_aura.h" |
| #include "ui/aura/window_tree_host.h" |
| #include "ui/events/event_rewriter.h" |
| #endif |
| |
| #if defined(OS_MACOSX) |
| #include "ui/base/test/scoped_preferred_scroller_style_mac.h" |
| #endif |
| |
| #if defined(OS_ANDROID) |
| #include "content/browser/renderer_host/render_widget_host_view_android.h" |
| #include "content/test/mock_overscroll_refresh_handler_android.h" |
| #endif |
| |
| namespace content { |
| |
| namespace { |
| |
| constexpr float kHitTestTolerance = 1.f; |
| |
| class TestInputEventObserver : public RenderWidgetHost::InputEventObserver { |
| public: |
| explicit TestInputEventObserver(RenderWidgetHost* host) : host_(host) { |
| host_->AddInputEventObserver(this); |
| } |
| |
| ~TestInputEventObserver() override { host_->RemoveInputEventObserver(this); } |
| |
| bool EventWasReceived() const { return !events_received_.empty(); } |
| void ResetEventsReceived() { events_received_.clear(); } |
| blink::WebInputEvent::Type EventType() const { |
| DCHECK(EventWasReceived()); |
| return events_received_.front(); |
| } |
| const std::vector<blink::WebInputEvent::Type>& events_received() { |
| return events_received_; |
| } |
| |
| const blink::WebInputEvent& event() const { return *event_; } |
| |
| void OnInputEvent(const blink::WebInputEvent& event) override { |
| events_received_.push_back(event.GetType()); |
| event_ = ui::WebInputEventTraits::Clone(event); |
| } |
| |
| const std::vector<InputEventAckSource>& events_acked() { |
| return events_acked_; |
| } |
| |
| void OnInputEventAck(InputEventAckSource source, |
| InputEventAckState state, |
| const blink::WebInputEvent&) override { |
| events_acked_.push_back(source); |
| } |
| |
| private: |
| RenderWidgetHost* host_; |
| std::vector<blink::WebInputEvent::Type> events_received_; |
| std::vector<InputEventAckSource> events_acked_; |
| ui::WebScopedInputEvent event_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestInputEventObserver); |
| }; |
| |
| // |position_in_widget| is in the coord space of |rwhv|. |
| template <typename PointType> |
| void SetWebEventPositions(blink::WebPointerProperties* event, |
| const PointType& position_in_widget, |
| RenderWidgetHostViewBase* rwhv, |
| RenderWidgetHostViewBase* rwhv_root) { |
| event->SetPositionInWidget(gfx::PointF(position_in_widget)); |
| const gfx::PointF position_in_root = |
| rwhv->TransformPointToRootCoordSpaceF(event->PositionInWidget()); |
| const gfx::PointF point_in_screen = |
| position_in_root + rwhv_root->GetViewBounds().OffsetFromOrigin(); |
| event->SetPositionInScreen(point_in_screen.x(), point_in_screen.y()); |
| } |
| |
| // For convenience when setting the position in the space of the root RWHV. |
| template <typename PointType> |
| void SetWebEventPositions(blink::WebPointerProperties* event, |
| const PointType& position_in_widget, |
| RenderWidgetHostViewBase* rwhv_root) { |
| DCHECK(!rwhv_root->IsRenderWidgetHostViewChildFrame()); |
| SetWebEventPositions(event, position_in_widget, rwhv_root, rwhv_root); |
| } |
| |
| #if defined(USE_AURA) |
| // |event->location()| is in the coord space of |rwhv|. |
| void UpdateEventRootLocation(ui::LocatedEvent* event, |
| RenderWidgetHostViewBase* rwhv, |
| RenderWidgetHostViewBase* rwhv_root) { |
| const gfx::Point position_in_root = |
| rwhv->TransformPointToRootCoordSpace(event->location()); |
| |
| gfx::Point root_location = position_in_root; |
| aura::Window::ConvertPointToTarget( |
| rwhv_root->GetNativeView(), rwhv_root->GetNativeView()->GetRootWindow(), |
| &root_location); |
| |
| event->set_root_location(root_location); |
| } |
| |
| // For convenience when setting the position in the space of the root RWHV. |
| void UpdateEventRootLocation(ui::LocatedEvent* event, |
| RenderWidgetHostViewBase* rwhv_root) { |
| DCHECK(!rwhv_root->IsRenderWidgetHostViewChildFrame()); |
| UpdateEventRootLocation(event, rwhv_root, rwhv_root); |
| } |
| #endif // defined(USE_AURA) |
| |
| void RouteMouseEventAndWaitUntilDispatch( |
| RenderWidgetHostInputEventRouter* router, |
| RenderWidgetHostViewBase* root_view, |
| RenderWidgetHostViewBase* expected_target, |
| blink::WebMouseEvent* event) { |
| InputEventAckWaiter waiter(expected_target->GetRenderWidgetHost(), |
| event->GetType()); |
| router->RouteMouseEvent(root_view, event, ui::LatencyInfo()); |
| waiter.Wait(); |
| } |
| |
| // Dispatch |event| to the specified view using browser process hit testing. |
| void DispatchMouseEventAndWaitUntilDispatch( |
| WebContentsImpl* web_contents, |
| blink::WebMouseEvent& event, |
| RenderWidgetHostViewBase* location_view, |
| const gfx::PointF& location, |
| RenderWidgetHostViewBase* expected_target, |
| const gfx::PointF& expected_location) { |
| auto* router = web_contents->GetInputEventRouter(); |
| |
| RenderWidgetHostMouseEventMonitor monitor( |
| expected_target->GetRenderWidgetHost()); |
| gfx::PointF root_location = |
| location_view->TransformPointToRootCoordSpaceF(location); |
| FrameTreeNode* root = web_contents->GetFrameTree()->root(); |
| auto* root_view = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| SetWebEventPositions(&event, root_location, root_view); |
| RouteMouseEventAndWaitUntilDispatch(router, root_view, expected_target, |
| &event); |
| EXPECT_TRUE(monitor.EventWasReceived()); |
| EXPECT_NEAR(expected_location.x(), monitor.event().PositionInWidget().x, |
| kHitTestTolerance) |
| << " & original location was " << location.x() << ", " << location.y() |
| << " & root_location was " << root_location.x() << ", " |
| << root_location.y(); |
| EXPECT_NEAR(expected_location.y(), monitor.event().PositionInWidget().y, |
| kHitTestTolerance); |
| } |
| |
| // Wrapper for the above method that creates a MouseDown to send. |
| void DispatchMouseEventAndWaitUntilDispatch( |
| WebContentsImpl* web_contents, |
| RenderWidgetHostViewBase* location_view, |
| const gfx::PointF& location, |
| RenderWidgetHostViewBase* expected_target, |
| const gfx::PointF& expected_location) { |
| blink::WebMouseEvent down_event( |
| blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| down_event.button = blink::WebPointerProperties::Button::kLeft; |
| down_event.click_count = 1; |
| DispatchMouseEventAndWaitUntilDispatch(web_contents, down_event, |
| location_view, location, |
| expected_target, expected_location); |
| } |
| |
| // Helper function that performs a surface hittest. |
| void SurfaceHitTestTestHelper( |
| Shell* shell, |
| net::test_server::EmbeddedTestServer* embedded_test_server) { |
| GURL main_url(embedded_test_server->GetURL( |
| "/frame_tree/page_with_positioned_frame.html")); |
| EXPECT_TRUE(NavigateToURL(shell, main_url)); |
| auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents()); |
| |
| // It is safe to obtain the root frame tree node here, as it doesn't change. |
| FrameTreeNode* root = web_contents->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| FrameTreeNode* child_node = root->child_at(0); |
| GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html")); |
| EXPECT_EQ(site_url, child_node->current_url()); |
| EXPECT_NE(shell->web_contents()->GetSiteInstance(), |
| child_node->current_frame_host()->GetSiteInstance()); |
| |
| RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>( |
| child_node->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host()); |
| |
| DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_child, |
| gfx::PointF(5, 5), rwhv_child, |
| gfx::PointF(5, 5)); |
| |
| DispatchMouseEventAndWaitUntilDispatch( |
| web_contents, rwhv_root, gfx::PointF(2, 2), rwhv_root, gfx::PointF(2, 2)); |
| } |
| |
| void OverlapSurfaceHitTestHelper( |
| Shell* shell, |
| net::test_server::EmbeddedTestServer* embedded_test_server) { |
| GURL main_url(embedded_test_server->GetURL( |
| "/frame_tree/page_with_content_overlap_positioned_frame.html")); |
| EXPECT_TRUE(NavigateToURL(shell, main_url)); |
| auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents()); |
| |
| // It is safe to obtain the root frame tree node here, as it doesn't change. |
| FrameTreeNode* root = web_contents->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| FrameTreeNode* child_node = root->child_at(0); |
| GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html")); |
| EXPECT_EQ(site_url, child_node->current_url()); |
| EXPECT_NE(shell->web_contents()->GetSiteInstance(), |
| child_node->current_frame_host()->GetSiteInstance()); |
| |
| RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>( |
| child_node->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host()); |
| |
| gfx::PointF parent_location = gfx::PointF(5, 5); |
| parent_location = |
| rwhv_child->TransformPointToRootCoordSpaceF(parent_location); |
| DispatchMouseEventAndWaitUntilDispatch( |
| web_contents, rwhv_child, gfx::PointF(5, 5), rwhv_root, parent_location); |
| |
| DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_child, |
| gfx::PointF(95, 95), rwhv_child, |
| gfx::PointF(95, 95)); |
| } |
| |
| void NonFlatTransformedSurfaceHitTestHelper( |
| Shell* shell, |
| net::test_server::EmbeddedTestServer* embedded_test_server) { |
| GURL main_url(embedded_test_server->GetURL( |
| "/frame_tree/page_with_non_flat_transformed_frame.html")); |
| EXPECT_TRUE(NavigateToURL(shell, main_url)); |
| auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents()); |
| |
| FrameTreeNode* root = web_contents->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| FrameTreeNode* child_node = root->child_at(0); |
| GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html")); |
| EXPECT_EQ(site_url, child_node->current_url()); |
| EXPECT_NE(shell->web_contents()->GetSiteInstance(), |
| child_node->current_frame_host()->GetSiteInstance()); |
| |
| RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>( |
| child_node->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host()); |
| |
| DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_child, |
| gfx::PointF(5, 5), rwhv_child, |
| gfx::PointF(5, 5)); |
| } |
| |
| void PerspectiveTransformedSurfaceHitTestHelper( |
| Shell* shell, |
| net::test_server::EmbeddedTestServer* embedded_test_server) { |
| GURL main_url(embedded_test_server->GetURL( |
| "/frame_tree/page_with_perspective_transformed_frame.html")); |
| EXPECT_TRUE(NavigateToURL(shell, main_url)); |
| auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents()); |
| |
| RenderFrameSubmissionObserver render_frame_submission_observer(web_contents); |
| |
| FrameTreeNode* root = web_contents->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| FrameTreeNode* child_node = root->child_at(0); |
| GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html")); |
| EXPECT_EQ(site_url, child_node->current_url()); |
| EXPECT_NE(shell->web_contents()->GetSiteInstance(), |
| child_node->current_frame_host()->GetSiteInstance()); |
| |
| RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>( |
| child_node->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host()); |
| |
| // (90, 75) hit tests into the child frame that is positioned at (50, 50). |
| // Without other transformations this should result in a translated point |
| // of (40, 25), but the 45 degree 3-dimensional rotation of the frame about |
| // a vertical axis skews it. |
| // We can't allow DispatchMouseEventAndWaitUntilDispatch to compute the |
| // coordinates in the root space unless browser conversions with |
| // perspective transforms are first fixed. See https://crbug.com/854257. |
| DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root, |
| gfx::PointF(90, 75), rwhv_child, |
| gfx::PointF(33, 23)); |
| } |
| |
| // Helper function that performs a surface hittest in nested frame. |
| void NestedSurfaceHitTestTestHelper( |
| Shell* shell, |
| net::test_server::EmbeddedTestServer* embedded_test_server) { |
| auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents()); |
| GURL main_url(embedded_test_server->GetURL( |
| "/frame_tree/page_with_positioned_nested_frames.html")); |
| EXPECT_TRUE(NavigateToURL(shell, main_url)); |
| |
| // It is safe to obtain the root frame tree node here, as it doesn't change. |
| FrameTreeNode* root = web_contents->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| FrameTreeNode* parent_iframe_node = root->child_at(0); |
| GURL site_url(embedded_test_server->GetURL( |
| "a.com", "/frame_tree/page_with_positioned_frame.html")); |
| EXPECT_EQ(site_url, parent_iframe_node->current_url()); |
| EXPECT_NE(shell->web_contents()->GetSiteInstance(), |
| parent_iframe_node->current_frame_host()->GetSiteInstance()); |
| |
| FrameTreeNode* nested_iframe_node = parent_iframe_node->child_at(0); |
| GURL nested_site_url(embedded_test_server->GetURL("baz.com", "/title1.html")); |
| EXPECT_EQ(nested_site_url, nested_iframe_node->current_url()); |
| EXPECT_NE(shell->web_contents()->GetSiteInstance(), |
| nested_iframe_node->current_frame_host()->GetSiteInstance()); |
| EXPECT_NE(parent_iframe_node->current_frame_host()->GetSiteInstance(), |
| nested_iframe_node->current_frame_host()->GetSiteInstance()); |
| |
| RenderWidgetHostViewBase* rwhv_nested = |
| static_cast<RenderWidgetHostViewBase*>( |
| nested_iframe_node->current_frame_host() |
| ->GetRenderWidgetHost() |
| ->GetView()); |
| |
| WaitForHitTestDataOrChildSurfaceReady( |
| nested_iframe_node->current_frame_host()); |
| |
| DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_nested, |
| gfx::PointF(10, 10), rwhv_nested, |
| gfx::PointF(10, 10)); |
| } |
| |
| void HitTestLayerSquashing( |
| Shell* shell, |
| net::test_server::EmbeddedTestServer* embedded_test_server) { |
| GURL main_url(embedded_test_server->GetURL( |
| "/frame_tree/oopif_hit_test_layer_squashing.html")); |
| EXPECT_TRUE(NavigateToURL(shell, main_url)); |
| auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents()); |
| |
| // It is safe to obtain the root frame tree node here, as it doesn't change. |
| FrameTreeNode* root = web_contents->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| FrameTreeNode* child_node = root->child_at(0); |
| GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html")); |
| EXPECT_EQ(site_url, child_node->current_url()); |
| EXPECT_NE(shell->web_contents()->GetSiteInstance(), |
| child_node->current_frame_host()->GetSiteInstance()); |
| |
| RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>( |
| child_node->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host()); |
| |
| gfx::Vector2dF child_offset = rwhv_child->GetViewBounds().origin() - |
| rwhv_root->GetViewBounds().origin(); |
| // Send a mouse-down on #B. The main-frame should receive it. |
| DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root, |
| gfx::PointF(195, 11), rwhv_root, |
| gfx::PointF(195, 11)); |
| // Send another event just below. The child-frame should receive it. |
| DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root, |
| gfx::PointF(195, 30), rwhv_child, |
| gfx::PointF(195, 30) - child_offset); |
| // Send a mouse-down on #C. |
| DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root, |
| gfx::PointF(35, 195), rwhv_root, |
| gfx::PointF(35, 195)); |
| // Send a mouse-down to the right of #C so that it goes to the child frame. |
| DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root, |
| gfx::PointF(55, 195), rwhv_child, |
| gfx::PointF(55, 195) - child_offset); |
| // Send a mouse-down to the right-bottom edge of the iframe. |
| DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root, |
| gfx::PointF(195, 235), rwhv_child, |
| gfx::PointF(195, 235) - child_offset); |
| } |
| |
| void HitTestWatermark( |
| Shell* shell, |
| net::test_server::EmbeddedTestServer* embedded_test_server) { |
| GURL main_url(embedded_test_server->GetURL( |
| "/frame_tree/oopif_hit_test_watermark.html")); |
| EXPECT_TRUE(NavigateToURL(shell, main_url)); |
| auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents()); |
| |
| // It is safe to obtain the root frame tree node here, as it doesn't change. |
| FrameTreeNode* root = web_contents->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| FrameTreeNode* child_node = root->child_at(0); |
| GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html")); |
| EXPECT_EQ(site_url, child_node->current_url()); |
| EXPECT_NE(shell->web_contents()->GetSiteInstance(), |
| child_node->current_frame_host()->GetSiteInstance()); |
| |
| RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>( |
| child_node->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host()); |
| |
| gfx::Vector2dF child_offset = rwhv_child->GetViewBounds().origin() - |
| rwhv_root->GetViewBounds().origin(); |
| const gfx::PointF child_location(100, 120); |
| // Send a mouse-down at the center of the iframe. This should go to the |
| // main-frame (since there's a translucent div on top of it). |
| DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_child, |
| child_location, rwhv_root, |
| child_location + child_offset); |
| |
| // Set 'pointer-events: none' on the div. |
| EXPECT_TRUE(ExecuteScript(web_contents, "W.style.pointerEvents = 'none';")); |
| |
| // TODO(sunxd): Re-enable this test when surface layer hit test is able to |
| // handle pointer-events none. See https://crbug.com/841358. |
| // Dispatch another event at the same location. It should reach the oopif this |
| // time. |
| if (!features::IsVizHitTestingSurfaceLayerEnabled()) { |
| DispatchMouseEventAndWaitUntilDispatch( |
| web_contents, rwhv_child, child_location, rwhv_child, child_location); |
| } |
| } |
| |
| #if defined(USE_AURA) |
| void HitTestRootWindowTransform( |
| Shell* shell, |
| net::test_server::EmbeddedTestServer* embedded_test_server) { |
| // Apply transform to root window to test that we respect root window |
| // transform when transforming event location. |
| gfx::Transform transform; |
| transform.RotateAboutXAxis(180.f); |
| transform.Translate(0.f, |
| -shell->window()->GetHost()->window()->bounds().height()); |
| shell->window()->GetHost()->SetRootTransform(transform); |
| |
| GURL main_url(embedded_test_server->GetURL( |
| "/frame_tree/page_with_positioned_frame.html")); |
| EXPECT_TRUE(NavigateToURL(shell, main_url)); |
| auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents()); |
| |
| // It is safe to obtain the root frame tree node here, as it doesn't change. |
| FrameTreeNode* root = web_contents->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| FrameTreeNode* child_node = root->child_at(0); |
| GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html")); |
| EXPECT_EQ(site_url, child_node->current_url()); |
| EXPECT_NE(shell->web_contents()->GetSiteInstance(), |
| child_node->current_frame_host()->GetSiteInstance()); |
| |
| RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>( |
| child_node->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host()); |
| |
| DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_child, |
| gfx::PointF(5, 5), rwhv_child, |
| gfx::PointF(5, 5)); |
| |
| DispatchMouseEventAndWaitUntilDispatch( |
| web_contents, rwhv_root, gfx::PointF(2, 2), rwhv_root, gfx::PointF(2, 2)); |
| } |
| #endif // defined(USE_AURA) |
| |
| #if defined(USE_AURA) |
| bool ConvertJSONToPoint(const std::string& str, gfx::PointF* point) { |
| std::unique_ptr<base::Value> value = base::JSONReader::ReadDeprecated(str); |
| if (!value) |
| return false; |
| base::DictionaryValue* root; |
| if (!value->GetAsDictionary(&root)) |
| return false; |
| double x, y; |
| if (!root->GetDouble("x", &x)) |
| return false; |
| if (!root->GetDouble("y", &y)) |
| return false; |
| point->set_x(x); |
| point->set_y(y); |
| return true; |
| } |
| |
| bool ConvertJSONToRect(const std::string& str, gfx::Rect* rect) { |
| std::unique_ptr<base::Value> value = base::JSONReader::ReadDeprecated(str); |
| if (!value) |
| return false; |
| base::DictionaryValue* root; |
| if (!value->GetAsDictionary(&root)) |
| return false; |
| int x, y, width, height; |
| if (!root->GetInteger("x", &x)) |
| return false; |
| if (!root->GetInteger("y", &y)) |
| return false; |
| if (!root->GetInteger("width", &width)) |
| return false; |
| if (!root->GetInteger("height", &height)) |
| return false; |
| rect->set_x(x); |
| rect->set_y(y); |
| rect->set_width(width); |
| rect->set_height(height); |
| return true; |
| } |
| #endif // defined(USE_AURA) |
| |
| // Class for intercepting SetMouseCapture messages being sent to a |
| // RenderWidgetHost. Note that this only works for RenderWidgetHosts that |
| // are attached to RenderFrameHosts, and not those for page popups, which |
| // use different bindings. |
| class SetMouseCaptureInterceptor |
| : public base::RefCountedThreadSafe<SetMouseCaptureInterceptor>, |
| public mojom::WidgetInputHandlerHostInterceptorForTesting { |
| public: |
| SetMouseCaptureInterceptor(RenderWidgetHostImpl* host) |
| : msg_received_(false), |
| capturing_(false), |
| host_(host), |
| impl_(binding().SwapImplForTesting(this)) {} |
| |
| bool Capturing() const { return capturing_; } |
| |
| void Wait() { |
| DCHECK(!run_loop_); |
| if (msg_received_) { |
| msg_received_ = false; |
| return; |
| } |
| run_loop_.reset(new base::RunLoop()); |
| run_loop_->Run(); |
| run_loop_.reset(); |
| msg_received_ = false; |
| } |
| |
| protected: |
| // mojom::WidgetInputHandlerHostInterceptorForTesting: |
| mojom::WidgetInputHandlerHost* GetForwardingInterface() override { |
| return impl_; |
| } |
| void SetMouseCapture(bool capturing) override { |
| capturing_ = capturing; |
| msg_received_ = true; |
| if (run_loop_) |
| run_loop_->Quit(); |
| GetForwardingInterface()->SetMouseCapture(capturing); |
| } |
| |
| private: |
| friend class base::RefCountedThreadSafe<SetMouseCaptureInterceptor>; |
| |
| ~SetMouseCaptureInterceptor() override { |
| binding().SwapImplForTesting(impl_); |
| } |
| |
| mojo::Binding<mojom::WidgetInputHandlerHost>& binding() { |
| return static_cast<InputRouterImpl*>(host_->input_router()) |
| ->frame_host_binding_for_testing(); |
| } |
| |
| std::unique_ptr<base::RunLoop> run_loop_; |
| bool msg_received_; |
| bool capturing_; |
| RenderWidgetHostImpl* host_; |
| mojom::WidgetInputHandlerHost* impl_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SetMouseCaptureInterceptor); |
| }; |
| |
| #if defined(USE_AURA) |
| // A class to allow intercepting and discarding of all system-level events |
| // that might otherwise cause unpredictable behaviour in tests. |
| class SystemEventRewriter : public ui::EventRewriter { |
| public: |
| // Helper class to allow events to pass through for the lifetime of the |
| // object. Use this when tests generate events. This is needed under mash |
| // because the generate events reach SystemEventRewriter and will be dropped |
| // if there is no ScopedAllow instance. |
| // Note that allowing system events can cause flakiness in browser tests that |
| // don't expect them. |
| class ScopedAllow { |
| public: |
| explicit ScopedAllow(SystemEventRewriter* rewriter) : rewriter_(rewriter) { |
| ++rewriter_->num_of_scoped_allows_; |
| } |
| ~ScopedAllow() { |
| DCHECK_GT(rewriter_->num_of_scoped_allows_, 0); |
| --rewriter_->num_of_scoped_allows_; |
| } |
| |
| private: |
| SystemEventRewriter* const rewriter_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ScopedAllow); |
| }; |
| |
| SystemEventRewriter() = default; |
| ~SystemEventRewriter() override = default; |
| |
| private: |
| ui::EventDispatchDetails RewriteEvent( |
| const ui::Event& event, |
| const Continuation continuation) override { |
| return num_of_scoped_allows_ ? SendEvent(continuation, &event) |
| : DiscardEvent(continuation); |
| } |
| |
| // Count of ScopedAllow objects. When it is greater than 0, events are allowed |
| // to pass. Otherwise, they are discarded. |
| int num_of_scoped_allows_ = 0; |
| |
| DISALLOW_COPY_AND_ASSIGN(SystemEventRewriter); |
| }; |
| #endif |
| |
| } // namespace |
| |
| class SitePerProcessHitTestBrowserTest |
| : public testing::WithParamInterface<std::tuple<int, float>>, |
| public SitePerProcessBrowserTest { |
| public: |
| SitePerProcessHitTestBrowserTest() {} |
| |
| #if defined(USE_AURA) |
| void PreRunTestOnMainThread() override { |
| SitePerProcessBrowserTest::PreRunTestOnMainThread(); |
| // Disable system mouse events, which can interfere with tests. |
| shell()->window()->GetHost()->AddEventRewriter(&event_rewriter_); |
| } |
| |
| void PostRunTestOnMainThread() override { |
| shell()->window()->GetHost()->RemoveEventRewriter(&event_rewriter_); |
| SitePerProcessBrowserTest::PostRunTestOnMainThread(); |
| } |
| #endif |
| |
| protected: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| SitePerProcessBrowserTest::SetUpCommandLine(command_line); |
| ui::PlatformEventSource::SetIgnoreNativePlatformEvents(true); |
| if (std::get<0>(GetParam()) == 1) { |
| feature_list_.InitAndEnableFeature(features::kEnableVizHitTestDrawQuad); |
| } else if (std::get<0>(GetParam()) == 2) { |
| feature_list_.InitWithFeatures({features::kEnableVizHitTestSurfaceLayer}, |
| {features::kEnableVizHitTestDrawQuad}); |
| } else { |
| feature_list_.InitWithFeatures({}, {features::kEnableVizHitTestDrawQuad, |
| features::kVizDisplayCompositor}); |
| } |
| } |
| |
| base::test::ScopedFeatureList feature_list_; |
| #if defined(USE_AURA) |
| SystemEventRewriter event_rewriter_; |
| #endif |
| }; |
| |
| // |
| // SitePerProcessHighDPIHitTestBrowserTest |
| // |
| |
| class SitePerProcessHighDPIHitTestBrowserTest |
| : public SitePerProcessHitTestBrowserTest { |
| public: |
| const double kDeviceScaleFactor = 2.0; |
| |
| SitePerProcessHighDPIHitTestBrowserTest() {} |
| |
| protected: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| SitePerProcessHitTestBrowserTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitchASCII( |
| switches::kForceDeviceScaleFactor, |
| base::StringPrintf("%f", kDeviceScaleFactor)); |
| } |
| }; |
| |
| // |
| // SitePerProcessNonIntegerScaleFactorHitTestBrowserTest |
| // |
| |
| class SitePerProcessNonIntegerScaleFactorHitTestBrowserTest |
| : public SitePerProcessHitTestBrowserTest { |
| public: |
| const double kDeviceScaleFactor = 1.5; |
| |
| SitePerProcessNonIntegerScaleFactorHitTestBrowserTest() {} |
| |
| protected: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| SitePerProcessHitTestBrowserTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitchASCII( |
| switches::kForceDeviceScaleFactor, |
| base::StringPrintf("%f", kDeviceScaleFactor)); |
| } |
| }; |
| |
| // Restrict to Aura to we can use routable MouseWheel event via |
| // RenderWidgetHostViewAura::OnScrollEvent(). |
| #if defined(USE_AURA) |
| class SitePerProcessInternalsHitTestBrowserTest |
| : public SitePerProcessHitTestBrowserTest { |
| public: |
| SitePerProcessInternalsHitTestBrowserTest() {} |
| |
| protected: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| SitePerProcessHitTestBrowserTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitch(switches::kExposeInternalsForTesting); |
| // Needed to guarantee the scrollable div we're testing with is not given |
| // its own compositing layer. |
| command_line->AppendSwitch(switches::kDisablePreferCompositingToLCDText); |
| command_line->AppendSwitchASCII( |
| switches::kForceDeviceScaleFactor, |
| base::StringPrintf("%f", std::get<1>(GetParam()))); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessInternalsHitTestBrowserTest, |
| ScrollNestedLocalNonFastScrollableDiv) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b)")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| // It is safe to obtain the root frame tree node here, as it doesn't change. |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree() |
| ->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| FrameTreeNode* parent_iframe_node = root->child_at(0); |
| |
| GURL site_url(embedded_test_server()->GetURL( |
| "b.com", "/tall_page_with_local_iframe.html")); |
| NavigateFrameToURL(parent_iframe_node, site_url); |
| |
| FrameTreeNode* nested_iframe_node = parent_iframe_node->child_at(0); |
| WaitForHitTestDataOrChildSurfaceReady( |
| nested_iframe_node->current_frame_host()); |
| |
| EXPECT_EQ( |
| " Site A ------------ proxies for B\n" |
| " +--Site B ------- proxies for A\n" |
| " +--Site B -- proxies for A\n" |
| "Where A = http://a.com/\n" |
| " B = http://b.com/", |
| DepictFrameTree(root)); |
| |
| const char* get_element_location_script_fmt = |
| "var rect = " |
| "document.getElementById('%s').getBoundingClientRect();\n" |
| "var point = {\n" |
| " x: rect.left,\n" |
| " y: rect.top\n" |
| "};\n" |
| "window.domAutomationController.send(JSON.stringify(point));"; |
| |
| // Since the nested local b-frame shares the RenderWidgetHostViewChildFrame |
| // with the parent frame, we need to query element offsets in both documents |
| // before converting to root space coordinates for the wheel event. |
| std::string str; |
| EXPECT_TRUE(ExecuteScriptAndExtractString( |
| nested_iframe_node->current_frame_host(), |
| base::StringPrintf(get_element_location_script_fmt, "scrollable_div"), |
| &str)); |
| gfx::PointF nested_point_f; |
| ConvertJSONToPoint(str, &nested_point_f); |
| |
| EXPECT_TRUE(ExecuteScriptAndExtractString( |
| parent_iframe_node->current_frame_host(), |
| base::StringPrintf(get_element_location_script_fmt, "nested_frame"), |
| &str)); |
| gfx::PointF parent_offset_f; |
| ConvertJSONToPoint(str, &parent_offset_f); |
| |
| // Compute location for wheel event. |
| gfx::PointF point_f(parent_offset_f.x() + nested_point_f.x() + 5.f, |
| parent_offset_f.y() + nested_point_f.y() + 5.f); |
| |
| RenderWidgetHostViewChildFrame* rwhv_nested = |
| static_cast<RenderWidgetHostViewChildFrame*>( |
| nested_iframe_node->current_frame_host() |
| ->GetRenderWidgetHost() |
| ->GetView()); |
| point_f = rwhv_nested->TransformPointToRootCoordSpaceF(point_f); |
| |
| RenderWidgetHostViewAura* rwhv_root = static_cast<RenderWidgetHostViewAura*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| gfx::PointF nested_in_parent; |
| rwhv_root->TransformPointToCoordSpaceForView( |
| point_f, |
| parent_iframe_node->current_frame_host() |
| ->GetRenderWidgetHost() |
| ->GetView(), |
| &nested_in_parent); |
| |
| // Get original scroll position. |
| double div_scroll_top_start; |
| EXPECT_TRUE(ExecuteScriptAndExtractDouble( |
| nested_iframe_node->current_frame_host(), |
| "window.domAutomationController.send(" |
| "document.getElementById('scrollable_div').scrollTop);", |
| &div_scroll_top_start)); |
| EXPECT_EQ(0.0, div_scroll_top_start); |
| |
| // Wait until renderer's compositor thread is synced. Otherwise the non fast |
| // scrollable regions won't be set when the event arrives. |
| MainThreadFrameObserver observer(rwhv_nested->GetRenderWidgetHost()); |
| observer.Wait(); |
| |
| // Send a wheel to scroll the div. |
| gfx::Point location(point_f.x(), point_f.y()); |
| ui::ScrollEvent scroll_event(ui::ET_SCROLL, location, ui::EventTimeForNow(), |
| 0, 0, -ui::MouseWheelEvent::kWheelDelta, 0, |
| ui::MouseWheelEvent::kWheelDelta, |
| 2); // This must be '2' or it gets silently |
| // dropped. |
| UpdateEventRootLocation(&scroll_event, rwhv_root); |
| |
| InputEventAckWaiter ack_observer( |
| parent_iframe_node->current_frame_host()->GetRenderWidgetHost(), |
| blink::WebInputEvent::kGestureScrollUpdate); |
| rwhv_root->OnScrollEvent(&scroll_event); |
| ack_observer.Wait(); |
| |
| // Check compositor layers. |
| EXPECT_TRUE(ExecuteScriptAndExtractString( |
| nested_iframe_node->current_frame_host(), |
| "window.domAutomationController.send(" |
| "window.internals.layerTreeAsText(document));", |
| &str)); |
| // We expect the nested OOPIF to not have any compositor layers. |
| EXPECT_EQ(std::string(), str); |
| |
| // Verify the div scrolled. |
| double div_scroll_top = div_scroll_top_start; |
| EXPECT_TRUE(ExecuteScriptAndExtractDouble( |
| nested_iframe_node->current_frame_host(), |
| "window.domAutomationController.send(" |
| "document.getElementById('scrollable_div').scrollTop);", |
| &div_scroll_top)); |
| EXPECT_NE(div_scroll_top_start, div_scroll_top); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessInternalsHitTestBrowserTest, |
| NestedLocalNonFastScrollableDivCoordsAreLocal) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b)")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| // It is safe to obtain the root frame tree node here, as it doesn't change. |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree() |
| ->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| FrameTreeNode* parent_iframe_node = root->child_at(0); |
| |
| GURL site_url(embedded_test_server()->GetURL( |
| "b.com", "/tall_page_with_local_iframe.html")); |
| NavigateFrameToURL(parent_iframe_node, site_url); |
| |
| FrameTreeNode* nested_iframe_node = parent_iframe_node->child_at(0); |
| WaitForHitTestDataOrChildSurfaceReady( |
| nested_iframe_node->current_frame_host()); |
| |
| EXPECT_EQ( |
| " Site A ------------ proxies for B\n" |
| " +--Site B ------- proxies for A\n" |
| " +--Site B -- proxies for A\n" |
| "Where A = http://a.com/\n" |
| " B = http://b.com/", |
| DepictFrameTree(root)); |
| |
| const char* get_element_location_script_fmt = |
| "var rect = " |
| "document.getElementById('%s').getBoundingClientRect();\n" |
| "var point = {\n" |
| " x: rect.left,\n" |
| " y: rect.top\n" |
| "};\n" |
| "window.domAutomationController.send(JSON.stringify(point));"; |
| |
| // Since the nested local b-frame shares the RenderWidgetHostViewChildFrame |
| // with the parent frame, we need to query element offsets in both documents |
| // before converting to root space coordinates for the wheel event. |
| std::string str; |
| EXPECT_TRUE(ExecuteScriptAndExtractString( |
| nested_iframe_node->current_frame_host(), |
| base::StringPrintf(get_element_location_script_fmt, "scrollable_div"), |
| &str)); |
| gfx::PointF nested_point_f; |
| ConvertJSONToPoint(str, &nested_point_f); |
| |
| int num_non_fast_region_rects; |
| EXPECT_TRUE(ExecuteScriptAndExtractInt( |
| parent_iframe_node->current_frame_host(), |
| "window.internals.markGestureScrollRegionDirty(document);\n" |
| "window.internals.forceCompositingUpdate(document);\n" |
| "var rects = window.internals.nonFastScrollableRects(document);\n" |
| "window.domAutomationController.send(rects.length);", |
| &num_non_fast_region_rects)); |
| EXPECT_EQ(1, num_non_fast_region_rects); |
| EXPECT_TRUE(ExecuteScriptAndExtractString( |
| parent_iframe_node->current_frame_host(), |
| "var rect = {\n" |
| " x: rects[0].left,\n" |
| " y: rects[0].top,\n" |
| " width: rects[0].width,\n" |
| " height: rects[0].height\n" |
| "};\n" |
| "window.domAutomationController.send(JSON.stringify(rect));", |
| &str)); |
| gfx::Rect non_fast_scrollable_rect_before_scroll; |
| ConvertJSONToRect(str, &non_fast_scrollable_rect_before_scroll); |
| |
| EXPECT_TRUE(ExecuteScriptAndExtractString( |
| parent_iframe_node->current_frame_host(), |
| base::StringPrintf(get_element_location_script_fmt, "nested_frame"), |
| &str)); |
| gfx::PointF parent_offset_f; |
| ConvertJSONToPoint(str, &parent_offset_f); |
| |
| // Compute location for wheel event to scroll the parent with respect to the |
| // mainframe. |
| gfx::PointF point_f(parent_offset_f.x() + 1.f, parent_offset_f.y() + 1.f); |
| |
| RenderWidgetHostViewChildFrame* rwhv_parent = |
| static_cast<RenderWidgetHostViewChildFrame*>( |
| parent_iframe_node->current_frame_host() |
| ->GetRenderWidgetHost() |
| ->GetView()); |
| point_f = rwhv_parent->TransformPointToRootCoordSpaceF(point_f); |
| |
| RenderWidgetHostViewAura* rwhv_root = static_cast<RenderWidgetHostViewAura*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| gfx::PointF nested_in_parent; |
| rwhv_root->TransformPointToCoordSpaceForView( |
| point_f, |
| parent_iframe_node->current_frame_host() |
| ->GetRenderWidgetHost() |
| ->GetView(), |
| &nested_in_parent); |
| |
| // Get original scroll position. |
| double div_scroll_top_start; |
| EXPECT_TRUE( |
| ExecuteScriptAndExtractDouble(parent_iframe_node->current_frame_host(), |
| "window.domAutomationController.send(" |
| "document.body.scrollTop);", |
| &div_scroll_top_start)); |
| EXPECT_EQ(0.0, div_scroll_top_start); |
| |
| // Send a wheel to scroll the parent containing the div. |
| gfx::Point location(point_f.x(), point_f.y()); |
| ui::ScrollEvent scroll_event(ui::ET_SCROLL, location, ui::EventTimeForNow(), |
| 0, 0, -ui::MouseWheelEvent::kWheelDelta, 0, |
| ui::MouseWheelEvent::kWheelDelta, |
| 2); // This must be '2' or it gets silently |
| // dropped. |
| UpdateEventRootLocation(&scroll_event, rwhv_root); |
| |
| InputEventAckWaiter ack_observer( |
| parent_iframe_node->current_frame_host()->GetRenderWidgetHost(), |
| blink::WebInputEvent::kGestureScrollUpdate); |
| rwhv_root->OnScrollEvent(&scroll_event); |
| ack_observer.Wait(); |
| |
| MainThreadFrameObserver thread_observer(rwhv_parent->GetRenderWidgetHost()); |
| thread_observer.Wait(); |
| |
| // Check compositor layers. |
| EXPECT_TRUE(ExecuteScriptAndExtractString( |
| nested_iframe_node->current_frame_host(), |
| "window.domAutomationController.send(" |
| "window.internals.layerTreeAsText(document));", |
| &str)); |
| // We expect the nested OOPIF to not have any compositor layers. |
| EXPECT_EQ(std::string(), str); |
| |
| // Verify the div scrolled. |
| double div_scroll_top = div_scroll_top_start; |
| EXPECT_TRUE( |
| ExecuteScriptAndExtractDouble(parent_iframe_node->current_frame_host(), |
| "window.domAutomationController.send(" |
| "document.body.scrollTop);", |
| &div_scroll_top)); |
| EXPECT_NE(div_scroll_top_start, div_scroll_top); |
| |
| // Verify the non-fast scrollable region rect is the same, even though the |
| // parent scroll isn't. |
| EXPECT_TRUE(ExecuteScriptAndExtractInt( |
| parent_iframe_node->current_frame_host(), |
| "window.internals.markGestureScrollRegionDirty(document);\n" |
| "window.internals.forceCompositingUpdate(document);\n" |
| "var rects = window.internals.nonFastScrollableRects(document);\n" |
| "window.domAutomationController.send(rects.length);", |
| &num_non_fast_region_rects)); |
| EXPECT_EQ(1, num_non_fast_region_rects); |
| EXPECT_TRUE(ExecuteScriptAndExtractString( |
| parent_iframe_node->current_frame_host(), |
| "var rect = {\n" |
| " x: rects[0].left,\n" |
| " y: rects[0].top,\n" |
| " width: rects[0].width,\n" |
| " height: rects[0].height\n" |
| "};\n" |
| "window.domAutomationController.send(JSON.stringify(rect));", |
| &str)); |
| gfx::Rect non_fast_scrollable_rect_after_scroll; |
| ConvertJSONToRect(str, &non_fast_scrollable_rect_after_scroll); |
| EXPECT_EQ(non_fast_scrollable_rect_before_scroll, |
| non_fast_scrollable_rect_after_scroll); |
| } |
| #endif // defined(USE_AURA) |
| |
| // Tests that wheel scroll bubbling gets cancelled when the wheel target view |
| // gets destroyed in the middle of a wheel scroll seqeunce. This happens in |
| // cases like overscroll navigation from inside an oopif. |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| CancelWheelScrollBubblingOnWheelTargetDeletion) { |
| ui::GestureConfiguration::GetInstance()->set_scroll_debounce_interval_in_ms( |
| 0); |
| GURL main_url(embedded_test_server()->GetURL( |
| "/frame_tree/page_with_positioned_frame.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| RenderFrameSubmissionObserver render_frame_submission_observer( |
| shell()->web_contents()); |
| |
| // It is safe to obtain the root frame tree node here, as it doesn't change. |
| FrameTreeNode* root = web_contents()->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| FrameTreeNode* iframe_node = root->child_at(0); |
| GURL site_url(embedded_test_server()->GetURL("baz.com", "/title1.html")); |
| EXPECT_EQ(site_url, iframe_node->current_url()); |
| |
| RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| RenderWidgetHostViewBase* child_rwhv = static_cast<RenderWidgetHostViewBase*>( |
| iframe_node->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| RenderWidgetHostInputEventRouter* router = |
| static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetInputEventRouter(); |
| |
| WaitForHitTestDataOrChildSurfaceReady(iframe_node->current_frame_host()); |
| |
| InputEventAckWaiter scroll_begin_observer( |
| root->current_frame_host()->GetRenderWidgetHost(), |
| blink::WebInputEvent::kGestureScrollBegin); |
| InputEventAckWaiter scroll_end_observer( |
| root->current_frame_host()->GetRenderWidgetHost(), |
| blink::WebInputEvent::kGestureScrollEnd); |
| |
| // Scroll the iframe upward, scroll events get bubbled up to the root. |
| blink::WebMouseWheelEvent scroll_event( |
| blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| gfx::Rect bounds = child_rwhv->GetViewBounds(); |
| float scale_factor = |
| render_frame_submission_observer.LastRenderFrameMetadata() |
| .page_scale_factor; |
| gfx::Point position_in_widget( |
| gfx::ToCeiledInt((bounds.x() - root_view->GetViewBounds().x() + 5) * |
| scale_factor), |
| gfx::ToCeiledInt((bounds.y() - root_view->GetViewBounds().y() + 5) * |
| scale_factor)); |
| SetWebEventPositions(&scroll_event, position_in_widget, root_view); |
| scroll_event.delta_x = 0.0f; |
| scroll_event.delta_y = 5.0f; |
| scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan; |
| scroll_event.has_precise_scrolling_deltas = true; |
| router->RouteMouseWheelEvent(root_view, &scroll_event, ui::LatencyInfo()); |
| scroll_begin_observer.Wait(); |
| |
| // Now destroy the child_rwhv, scroll bubbling stops and a GSE gets sent to |
| // the root_view. |
| RenderProcessHost* rph = |
| iframe_node->current_frame_host()->GetSiteInstance()->GetProcess(); |
| RenderProcessHostWatcher crash_observer( |
| rph, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); |
| EXPECT_TRUE(rph->Shutdown(0)); |
| crash_observer.Wait(); |
| scroll_event.delta_y = 0.0f; |
| scroll_event.phase = blink::WebMouseWheelEvent::kPhaseEnded; |
| scroll_event.dispatch_type = |
| blink::WebInputEvent::DispatchType::kEventNonBlocking; |
| router->RouteMouseWheelEvent(root_view, &scroll_event, ui::LatencyInfo()); |
| scroll_end_observer.Wait(); |
| } |
| |
| // Ensure that the positions of touch events sent to cross-process subframes |
| // account for any change in the position of the subframe during the scroll |
| // sequence. |
| // Before the issue fix, we record the transform for root to subframe coordinate |
| // space and reuse it in the sequence. It is wrong if the subframe moved in the |
| // sequence. In this test, the point passed to subframe at the touch end (scroll |
| // end) would be wrong because the subframe moved in scroll. |
| // Suppose the offset of subframe in rootframe is (0, 0) in the test, the touch |
| // start position in root is (15, 15) same in subframe, then move to (15, 10) |
| // in rootframe and subframe it caused subframe scroll down for 5px, then touch |
| // release in (15, 10) same as the touch move in root frame. Before the fix the |
| // touch end would pass (15, 10) to subframe which should be (15, 15) in |
| // subframe. |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| TouchAndGestureEventPositionChange) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "/frame_tree/page_with_tall_positioned_frame.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| FrameTreeNode* root = web_contents()->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| auto* root_rwhv = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| // Synchronize with the child and parent renderers to guarantee that the |
| // surface information required for event hit testing is ready. |
| RenderWidgetHostViewChildFrame* child_rwhv = |
| static_cast<RenderWidgetHostViewChildFrame*>( |
| root->child_at(0)->current_frame_host()->GetView()); |
| WaitForHitTestDataOrChildSurfaceReady( |
| root->child_at(0)->current_frame_host()); |
| |
| RenderFrameSubmissionObserver render_frame_submission_observer( |
| shell()->web_contents()); |
| |
| RenderWidgetHostInputEventRouter* router = |
| web_contents()->GetInputEventRouter(); |
| |
| const float scale_factor = |
| render_frame_submission_observer.LastRenderFrameMetadata() |
| .page_scale_factor; |
| |
| auto await_touch_event_with_position = base::BindRepeating( |
| [](blink::WebInputEvent::Type expected_type, |
| RenderWidgetHostViewBase* rwhv, gfx::PointF expected_position, |
| gfx::PointF expected_position_in_root, InputEventAckSource, |
| InputEventAckState, const blink::WebInputEvent& event) { |
| if (event.GetType() != expected_type) |
| return false; |
| |
| const auto& touch_event = |
| static_cast<const blink::WebTouchEvent&>(event); |
| const gfx::PointF root_point = rwhv->TransformPointToRootCoordSpaceF( |
| touch_event.touches[0].PositionInWidget()); |
| |
| EXPECT_NEAR(touch_event.touches[0].PositionInWidget().x, |
| expected_position.x(), 1.0f); |
| EXPECT_NEAR(touch_event.touches[0].PositionInWidget().y, |
| expected_position.y(), 1.0f); |
| EXPECT_NEAR(root_point.x(), expected_position_in_root.x(), 1.0f); |
| EXPECT_NEAR(root_point.y(), expected_position_in_root.y(), 1.0f); |
| return true; |
| }); |
| |
| auto await_gesture_event_with_position = base::BindRepeating( |
| [](blink::WebInputEvent::Type expected_type, |
| RenderWidgetHostViewBase* rwhv, gfx::PointF expected_position, |
| gfx::PointF expected_position_in_root, InputEventAckSource, |
| InputEventAckState, const blink::WebInputEvent& event) { |
| if (event.GetType() != expected_type) |
| return false; |
| |
| const auto& gesture_event = |
| static_cast<const blink::WebGestureEvent&>(event); |
| const gfx::PointF root_point = rwhv->TransformPointToRootCoordSpaceF( |
| gesture_event.PositionInWidget()); |
| |
| EXPECT_NEAR(gesture_event.PositionInWidget().x, expected_position.x(), |
| 1.0f); |
| EXPECT_NEAR(gesture_event.PositionInWidget().y, expected_position.y(), |
| 1.0f); |
| EXPECT_NEAR(root_point.x(), expected_position_in_root.x(), 1.0f); |
| EXPECT_NEAR(root_point.y(), expected_position_in_root.y(), 1.0f); |
| return true; |
| }); |
| |
| MainThreadFrameObserver thread_observer(root_rwhv->GetRenderWidgetHost()); |
| |
| gfx::PointF touch_start_point_in_child(15, 15); |
| gfx::PointF touch_move_point_in_child(15, 10); |
| |
| gfx::PointF touch_start_point = |
| child_rwhv->TransformPointToRootCoordSpaceF(touch_start_point_in_child); |
| gfx::PointF touch_move_point = |
| child_rwhv->TransformPointToRootCoordSpaceF(touch_move_point_in_child); |
| |
| // Touch start |
| { |
| blink::WebTouchEvent touch_start_event( |
| blink::WebInputEvent::kTouchStart, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| touch_start_event.touches_length = 1; |
| touch_start_event.touches[0].state = blink::WebTouchPoint::kStatePressed; |
| touch_start_event.touches[0].SetPositionInWidget(touch_start_point); |
| touch_start_event.unique_touch_event_id = 1; |
| |
| InputEventAckWaiter await_begin_in_child( |
| child_rwhv->GetRenderWidgetHost(), |
| base::BindRepeating(await_touch_event_with_position, |
| blink::WebInputEvent::kTouchStart, child_rwhv, |
| touch_start_point_in_child, touch_start_point)); |
| |
| router->RouteTouchEvent(root_rwhv, &touch_start_event, |
| ui::LatencyInfo(ui::SourceEventType::TOUCH)); |
| |
| await_begin_in_child.Wait(); |
| |
| blink::WebGestureEvent gesture_tap_event( |
| blink::WebInputEvent::kGestureTapDown, |
| blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests(), |
| blink::kWebGestureDeviceTouchscreen); |
| gesture_tap_event.unique_touch_event_id = 1; |
| gesture_tap_event.SetPositionInWidget(touch_start_point); |
| InputEventAckWaiter await_tap_in_child( |
| child_rwhv->GetRenderWidgetHost(), |
| base::BindRepeating(await_gesture_event_with_position, |
| blink::WebInputEvent::kGestureTapDown, child_rwhv, |
| touch_start_point_in_child, touch_start_point)); |
| router->RouteGestureEvent(root_rwhv, &gesture_tap_event, |
| ui::LatencyInfo(ui::SourceEventType::TOUCH)); |
| await_tap_in_child.Wait(); |
| } |
| |
| // Touch move |
| { |
| blink::WebTouchEvent touch_move_event( |
| blink::WebInputEvent::kTouchMove, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| touch_move_event.touches_length = 1; |
| touch_move_event.touches[0].state = blink::WebTouchPoint::kStateMoved; |
| touch_move_event.touches[0].SetPositionInWidget(touch_move_point); |
| touch_move_event.unique_touch_event_id = 2; |
| InputEventAckWaiter await_move_in_child( |
| child_rwhv->GetRenderWidgetHost(), |
| base::BindRepeating(await_touch_event_with_position, |
| blink::WebInputEvent::kTouchMove, child_rwhv, |
| touch_move_point_in_child, touch_move_point)); |
| router->RouteTouchEvent(root_rwhv, &touch_move_event, |
| ui::LatencyInfo(ui::SourceEventType::TOUCH)); |
| await_move_in_child.Wait(); |
| } |
| |
| // Gesture Begin and update |
| { |
| blink::WebGestureEvent gesture_scroll_begin( |
| blink::WebGestureEvent::kGestureScrollBegin, |
| blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests(), |
| blink::kWebGestureDeviceTouchscreen); |
| gesture_scroll_begin.unique_touch_event_id = 2; |
| gesture_scroll_begin.data.scroll_begin.delta_hint_units = |
| blink::WebGestureEvent::ScrollUnits::kPrecisePixels; |
| gesture_scroll_begin.data.scroll_begin.delta_x_hint = 0.f; |
| gesture_scroll_begin.data.scroll_begin.delta_y_hint = -5.f * scale_factor; |
| gesture_scroll_begin.SetPositionInWidget(touch_start_point); |
| |
| blink::WebGestureEvent gesture_scroll_update( |
| blink::WebGestureEvent::kGestureScrollUpdate, |
| blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests(), |
| blink::kWebGestureDeviceTouchscreen); |
| gesture_scroll_update.unique_touch_event_id = 2; |
| gesture_scroll_update.data.scroll_update.delta_units = |
| blink::WebGestureEvent::ScrollUnits::kPrecisePixels; |
| gesture_scroll_update.data.scroll_update.delta_x = 0.f; |
| gesture_scroll_update.data.scroll_update.delta_y = -5.f * scale_factor; |
| gesture_scroll_update.SetPositionInWidget(touch_start_point); |
| |
| InputEventAckWaiter await_begin_in_child( |
| child_rwhv->GetRenderWidgetHost(), |
| base::BindRepeating(await_gesture_event_with_position, |
| blink::WebInputEvent::kGestureScrollBegin, |
| child_rwhv, touch_start_point_in_child, |
| touch_start_point)); |
| InputEventAckWaiter await_update_in_child( |
| child_rwhv->GetRenderWidgetHost(), |
| base::BindRepeating(await_gesture_event_with_position, |
| blink::WebInputEvent::kGestureScrollUpdate, |
| child_rwhv, touch_start_point_in_child, |
| touch_start_point)); |
| InputEventAckWaiter await_update_in_root( |
| root_rwhv->GetRenderWidgetHost(), |
| base::BindRepeating(await_gesture_event_with_position, |
| blink::WebInputEvent::kGestureScrollUpdate, |
| root_rwhv, touch_start_point, touch_start_point)); |
| |
| router->RouteGestureEvent(root_rwhv, &gesture_scroll_begin, |
| ui::LatencyInfo(ui::SourceEventType::TOUCH)); |
| await_begin_in_child.Wait(); |
| router->RouteGestureEvent(root_rwhv, &gesture_scroll_update, |
| ui::LatencyInfo(ui::SourceEventType::TOUCH)); |
| await_update_in_child.Wait(); |
| await_update_in_root.Wait(); |
| thread_observer.Wait(); |
| } |
| |
| // Touch end & Scroll end |
| { |
| blink::WebTouchEvent touch_end_event( |
| blink::WebInputEvent::kTouchEnd, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| touch_end_event.touches_length = 1; |
| touch_end_event.touches[0].state = blink::WebTouchPoint::kStateReleased; |
| touch_end_event.touches[0].SetPositionInWidget(touch_move_point); |
| touch_end_event.unique_touch_event_id = 3; |
| InputEventAckWaiter await_end_in_child( |
| child_rwhv->GetRenderWidgetHost(), |
| base::BindRepeating(await_touch_event_with_position, |
| blink::WebInputEvent::kTouchEnd, child_rwhv, |
| touch_start_point_in_child, touch_move_point)); |
| router->RouteTouchEvent(root_rwhv, &touch_end_event, |
| ui::LatencyInfo(ui::SourceEventType::TOUCH)); |
| await_end_in_child.Wait(); |
| |
| blink::WebGestureEvent gesture_scroll_end( |
| blink::WebGestureEvent::kGestureScrollEnd, |
| blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests(), |
| blink::kWebGestureDeviceTouchscreen); |
| gesture_scroll_end.unique_touch_event_id = 3; |
| gesture_scroll_end.data.scroll_end.delta_units = |
| blink::WebGestureEvent::ScrollUnits::kPrecisePixels; |
| gesture_scroll_end.SetPositionInWidget(touch_move_point); |
| |
| InputEventAckWaiter await_scroll_end_in_child( |
| child_rwhv->GetRenderWidgetHost(), |
| base::BindRepeating(await_gesture_event_with_position, |
| blink::WebInputEvent::kGestureScrollEnd, child_rwhv, |
| touch_start_point_in_child, touch_move_point)); |
| router->RouteGestureEvent(root_rwhv, &gesture_scroll_end, |
| ui::LatencyInfo(ui::SourceEventType::TOUCH)); |
| await_scroll_end_in_child.Wait(); |
| |
| thread_observer.Wait(); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| CSSTransformedIframeTouchEventCoordinates) { |
| // This test only makes sense if viz hit testing is enabled. |
| if (std::get<0>(GetParam()) == 0) |
| return; |
| |
| GURL url(embedded_test_server()->GetURL( |
| "/frame_tree/page_with_positioned_scaled_frame.html")); |
| ASSERT_TRUE(NavigateToURL(shell(), url)); |
| |
| RenderFrameSubmissionObserver render_frame_submission_observer( |
| shell()->web_contents()); |
| |
| FrameTreeNode* root_frame_tree_node = web_contents()->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root_frame_tree_node->child_count()); |
| FrameTreeNode* child_frame_tree_node = root_frame_tree_node->child_at(0); |
| GURL child_url(embedded_test_server()->GetURL("baz.com", "/title1.html")); |
| EXPECT_EQ(child_url, child_frame_tree_node->current_url()); |
| |
| auto* root_rwhv = static_cast<RenderWidgetHostViewBase*>( |
| root_frame_tree_node->current_frame_host() |
| ->GetRenderWidgetHost() |
| ->GetView()); |
| auto* child_rwhv = static_cast<RenderWidgetHostViewBase*>( |
| child_frame_tree_node->current_frame_host() |
| ->GetRenderWidgetHost() |
| ->GetView()); |
| |
| WaitForHitTestDataOrChildSurfaceReady( |
| child_frame_tree_node->current_frame_host()); |
| |
| const float scale_factor = |
| render_frame_submission_observer.LastRenderFrameMetadata() |
| .page_scale_factor; |
| |
| // Some basic tests on the transforms between child and root. These assume |
| // a CSS scale of 0.5 on the child, though should be robust to placement of |
| // the iframe. |
| float kScaleTolerance = 0.0001f; |
| gfx::Transform transform_to_child; |
| ASSERT_TRUE( |
| root_rwhv->GetTransformToViewCoordSpace(child_rwhv, &transform_to_child)); |
| EXPECT_TRUE(transform_to_child.IsScaleOrTranslation()); |
| EXPECT_NEAR(2.f / scale_factor, transform_to_child.matrix().getFloat(0, 0), |
| kScaleTolerance); |
| EXPECT_NEAR(2.f / scale_factor, transform_to_child.matrix().getFloat(1, 1), |
| kScaleTolerance); |
| |
| gfx::PointF child_origin = |
| child_rwhv->TransformPointToRootCoordSpaceF(gfx::PointF()); |
| |
| gfx::Transform transform_from_child; |
| ASSERT_TRUE(child_rwhv->GetTransformToViewCoordSpace(root_rwhv, |
| &transform_from_child)); |
| EXPECT_TRUE(transform_from_child.IsScaleOrTranslation()); |
| EXPECT_NEAR(0.5f * scale_factor, transform_from_child.matrix().getFloat(0, 0), |
| kScaleTolerance); |
| EXPECT_NEAR(0.5f * scale_factor, transform_from_child.matrix().getFloat(1, 1), |
| kScaleTolerance); |
| EXPECT_EQ(child_origin.x(), transform_from_child.matrix().getFloat(0, 3)); |
| EXPECT_EQ(child_origin.y(), transform_from_child.matrix().getFloat(1, 3)); |
| |
| gfx::Transform transform_child_to_child = |
| transform_from_child * transform_to_child; |
| // If the scale factor is 1.f, then this multiplication of the transform with |
| // its inverse will be exact, and IsIdentity will indicate that. However, if |
| // the scale is an arbitrary float (as on Android), then we instead compare |
| // element by element using EXPECT_NEAR. |
| if (scale_factor == 1.f) { |
| EXPECT_TRUE(transform_child_to_child.IsIdentity()); |
| } else { |
| const float kTolerance = 0.001f; |
| const int kDim = 4; |
| for (int row = 0; row < kDim; ++row) { |
| for (int col = 0; col < kDim; ++col) { |
| EXPECT_NEAR(row == col ? 1.f : 0.f, |
| transform_child_to_child.matrix().getFloat(row, col), |
| kTolerance); |
| } |
| } |
| } |
| |
| gfx::Transform transform_root_to_root; |
| ASSERT_TRUE(root_rwhv->GetTransformToViewCoordSpace(root_rwhv, |
| &transform_root_to_root)); |
| EXPECT_TRUE(transform_root_to_root.IsIdentity()); |
| |
| // Select two points inside child, one for the touch start and a different |
| // one for a touch move. |
| gfx::PointF touch_start_point_in_child(6, 6); |
| gfx::PointF touch_move_point_in_child(10, 10); |
| |
| gfx::PointF touch_start_point = |
| child_rwhv->TransformPointToRootCoordSpaceF(touch_start_point_in_child); |
| gfx::PointF touch_move_point = |
| child_rwhv->TransformPointToRootCoordSpaceF(touch_move_point_in_child); |
| |
| // Install InputEventObserver on child, and collect the three events. |
| TestInputEventObserver child_event_observer( |
| child_rwhv->GetRenderWidgetHost()); |
| InputEventAckWaiter child_touch_start_waiter( |
| child_rwhv->GetRenderWidgetHost(), blink::WebInputEvent::kTouchStart); |
| InputEventAckWaiter child_touch_move_waiter(child_rwhv->GetRenderWidgetHost(), |
| blink::WebInputEvent::kTouchMove); |
| InputEventAckWaiter child_touch_end_waiter(child_rwhv->GetRenderWidgetHost(), |
| blink::WebInputEvent::kTouchEnd); |
| |
| // Send events and verify each one was sent to the child with correctly |
| // transformed event coordinates. |
| auto* router = web_contents()->GetInputEventRouter(); |
| const float kCoordinateTolerance = 0.1f; |
| |
| // TouchStart. |
| blink::WebTouchEvent touch_start_event( |
| blink::WebInputEvent::kTouchStart, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| touch_start_event.touches_length = 1; |
| touch_start_event.touches[0].state = blink::WebTouchPoint::kStatePressed; |
| SetWebEventPositions(&touch_start_event.touches[0], touch_start_point, |
| root_rwhv); |
| touch_start_event.unique_touch_event_id = 1; |
| router->RouteTouchEvent(root_rwhv, &touch_start_event, |
| ui::LatencyInfo(ui::SourceEventType::TOUCH)); |
| child_touch_start_waiter.Wait(); |
| |
| ASSERT_EQ(1U, child_event_observer.events_received().size()); |
| ASSERT_EQ(blink::WebInputEvent::kTouchStart, |
| child_event_observer.event().GetType()); |
| const blink::WebTouchEvent& touch_start_event_received = |
| static_cast<const blink::WebTouchEvent&>(child_event_observer.event()); |
| EXPECT_NEAR(touch_start_point_in_child.x(), |
| touch_start_event_received.touches[0].PositionInWidget().x, |
| kCoordinateTolerance); |
| EXPECT_NEAR(touch_start_point_in_child.y(), |
| touch_start_event_received.touches[0].PositionInWidget().y, |
| kCoordinateTolerance); |
| |
| // TouchMove. |
| blink::WebTouchEvent touch_move_event( |
| blink::WebInputEvent::kTouchMove, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| touch_move_event.touches_length = 1; |
| touch_move_event.touches[0].state = blink::WebTouchPoint::kStateMoved; |
| SetWebEventPositions(&touch_move_event.touches[0], touch_move_point, |
| root_rwhv); |
| touch_move_event.unique_touch_event_id = 2; |
| router->RouteTouchEvent(root_rwhv, &touch_move_event, |
| ui::LatencyInfo(ui::SourceEventType::TOUCH)); |
| child_touch_move_waiter.Wait(); |
| |
| ASSERT_EQ(2U, child_event_observer.events_received().size()); |
| ASSERT_EQ(blink::WebInputEvent::kTouchMove, |
| child_event_observer.event().GetType()); |
| const blink::WebTouchEvent& touch_move_event_received = |
| static_cast<const blink::WebTouchEvent&>(child_event_observer.event()); |
| EXPECT_NEAR(touch_move_point_in_child.x(), |
| touch_move_event_received.touches[0].PositionInWidget().x, |
| kCoordinateTolerance); |
| EXPECT_NEAR(touch_move_point_in_child.y(), |
| touch_move_event_received.touches[0].PositionInWidget().y, |
| kCoordinateTolerance); |
| |
| // TouchEnd. |
| blink::WebTouchEvent touch_end_event( |
| blink::WebInputEvent::kTouchEnd, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| touch_end_event.touches_length = 1; |
| touch_end_event.touches[0].state = blink::WebTouchPoint::kStateReleased; |
| SetWebEventPositions(&touch_end_event.touches[0], touch_move_point, |
| root_rwhv); |
| touch_end_event.unique_touch_event_id = 3; |
| router->RouteTouchEvent(root_rwhv, &touch_end_event, |
| ui::LatencyInfo(ui::SourceEventType::TOUCH)); |
| child_touch_end_waiter.Wait(); |
| |
| ASSERT_EQ(3U, child_event_observer.events_received().size()); |
| ASSERT_EQ(blink::WebInputEvent::kTouchEnd, |
| child_event_observer.event().GetType()); |
| const blink::WebTouchEvent& touch_end_event_received = |
| static_cast<const blink::WebTouchEvent&>(child_event_observer.event()); |
| EXPECT_NEAR(touch_move_point_in_child.x(), |
| touch_end_event_received.touches[0].PositionInWidget().x, |
| kCoordinateTolerance); |
| EXPECT_NEAR(touch_move_point_in_child.y(), |
| touch_end_event_received.touches[0].PositionInWidget().y, |
| kCoordinateTolerance); |
| } |
| |
| // When a scroll event is bubbled, ensure that the bubbled event's coordinates |
| // are correctly updated to the ancestor's coordinate space. In particular, |
| // ensure that the transformation considers CSS scaling of the child where |
| // simply applying the ancestor's offset does not produce the correct |
| // coordinates in the ancestor's coordinate space. |
| // See https://crbug.com/817392 |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| BubbledScrollEventsTransformedCorrectly) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "/frame_tree/page_with_positioned_scaled_frame.html")); |
| ASSERT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| RenderFrameSubmissionObserver render_frame_submission_observer( |
| shell()->web_contents()); |
| |
| // It is safe to obtain the root frame tree node here, as it doesn't change. |
| FrameTreeNode* root = web_contents()->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| FrameTreeNode* iframe_node = root->child_at(0); |
| GURL site_url(embedded_test_server()->GetURL("baz.com", "/title1.html")); |
| EXPECT_EQ(site_url, iframe_node->current_url()); |
| |
| RenderWidgetHostViewBase* root_rwhv = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| RenderWidgetHostInputEventRouter* router = |
| static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetInputEventRouter(); |
| |
| WaitForHitTestDataOrChildSurfaceReady(iframe_node->current_frame_host()); |
| |
| const float scale_factor = |
| render_frame_submission_observer.LastRenderFrameMetadata() |
| .page_scale_factor; |
| // Due to the CSS scaling of the iframe, the position in the child view's |
| // coordinates is (96, 96) and not (48, 48) (or approximately these values |
| // if there's rounding due to the scale factor). |
| const gfx::Point position_in_root(gfx::ToCeiledInt(150 * scale_factor), |
| gfx::ToCeiledInt(150 * scale_factor)); |
| |
| auto expect_gsb_with_position = base::BindRepeating( |
| [](const gfx::Point& expected_position, content::InputEventAckSource, |
| content::InputEventAckState, const blink::WebInputEvent& event) { |
| if (event.GetType() != blink::WebInputEvent::kGestureScrollBegin) |
| return false; |
| |
| const blink::WebGestureEvent& gesture_event = |
| static_cast<const blink::WebGestureEvent&>(event); |
| EXPECT_NEAR(expected_position.x(), gesture_event.PositionInWidget().x, |
| kHitTestTolerance); |
| EXPECT_NEAR(expected_position.y(), gesture_event.PositionInWidget().y, |
| kHitTestTolerance); |
| return true; |
| }); |
| |
| InputEventAckWaiter root_scroll_begin_observer( |
| root_rwhv->GetRenderWidgetHost(), |
| base::BindRepeating(expect_gsb_with_position, position_in_root)); |
| |
| // Scroll the iframe upward, scroll events get bubbled up to the root. |
| blink::WebMouseWheelEvent scroll_event( |
| blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| SetWebEventPositions(&scroll_event, position_in_root, root_rwhv); |
| scroll_event.delta_x = 0.0f; |
| scroll_event.delta_y = 5.0f; |
| scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan; |
| scroll_event.has_precise_scrolling_deltas = true; |
| |
| router->RouteMouseWheelEvent(root_rwhv, &scroll_event, ui::LatencyInfo()); |
| |
| root_scroll_begin_observer.Wait(); |
| } |
| |
| namespace { |
| |
| // Waits until an event of the given type has been sent to the given |
| // RenderWidgetHost. |
| class OutgoingEventWaiter : public RenderWidgetHost::InputEventObserver { |
| public: |
| explicit OutgoingEventWaiter(RenderWidgetHostImpl* rwh, |
| blink::WebInputEvent::Type type) |
| : rwh_(rwh->GetWeakPtr()), type_(type) { |
| rwh->AddInputEventObserver(this); |
| } |
| |
| ~OutgoingEventWaiter() override { |
| if (rwh_) |
| rwh_->RemoveInputEventObserver(this); |
| } |
| |
| void OnInputEvent(const blink::WebInputEvent& event) override { |
| if (event.GetType() == type_) { |
| seen_event_ = true; |
| if (quit_closure_) |
| std::move(quit_closure_).Run(); |
| } |
| } |
| |
| void Wait() { |
| if (!seen_event_) { |
| base::RunLoop run_loop; |
| quit_closure_ = run_loop.QuitClosure(); |
| run_loop.Run(); |
| } |
| } |
| |
| private: |
| base::WeakPtr<RenderWidgetHostImpl> rwh_; |
| const blink::WebInputEvent::Type type_; |
| bool seen_event_ = false; |
| base::OnceClosure quit_closure_; |
| }; |
| |
| // Fails the test if an event of the given type is sent to the given |
| // RenderWidgetHost. |
| class BadInputEventObserver : public RenderWidgetHost::InputEventObserver { |
| public: |
| explicit BadInputEventObserver(RenderWidgetHostImpl* rwh, |
| blink::WebInputEvent::Type type) |
| : rwh_(rwh->GetWeakPtr()), type_(type) { |
| rwh->AddInputEventObserver(this); |
| } |
| |
| ~BadInputEventObserver() override { |
| if (rwh_) |
| rwh_->RemoveInputEventObserver(this); |
| } |
| |
| void OnInputEvent(const blink::WebInputEvent& event) override { |
| EXPECT_NE(type_, event.GetType()) |
| << "Unexpected " << blink::WebInputEvent::GetName(event.GetType()); |
| } |
| |
| private: |
| base::WeakPtr<RenderWidgetHostImpl> rwh_; |
| const blink::WebInputEvent::Type type_; |
| }; |
| |
| } // namespace |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| ScrollBubblingTargetWithUnrelatedGesture) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "/frame_tree/page_with_positioned_nested_frames.html")); |
| ASSERT_TRUE(NavigateToURL(shell(), main_url)); |
| FrameTreeNode* root = web_contents()->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| FrameTreeNode* parent_iframe_node = root->child_at(0); |
| ASSERT_EQ(1U, parent_iframe_node->child_count()); |
| |
| GURL nested_frame_url(embedded_test_server()->GetURL( |
| "baz.com", "/page_with_touch_start_janking_main_thread.html")); |
| NavigateFrameToURL(parent_iframe_node->child_at(0), nested_frame_url); |
| |
| RenderWidgetHostViewBase* root_rwhv = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| RenderWidgetHostViewChildFrame* rwhv_parent = |
| static_cast<RenderWidgetHostViewChildFrame*>( |
| parent_iframe_node->current_frame_host() |
| ->GetRenderWidgetHost() |
| ->GetView()); |
| RenderWidgetHostViewChildFrame* rwhv_nested = |
| static_cast<RenderWidgetHostViewChildFrame*>( |
| parent_iframe_node->child_at(0) |
| ->current_frame_host() |
| ->GetRenderWidgetHost() |
| ->GetView()); |
| |
| RenderWidgetHostInputEventRouter* router = |
| static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetInputEventRouter(); |
| |
| WaitForHitTestDataOrChildSurfaceReady( |
| parent_iframe_node->child_at(0)->current_frame_host()); |
| |
| OutgoingEventWaiter outgoing_touch_end_waiter( |
| static_cast<RenderWidgetHostImpl*>(rwhv_nested->GetRenderWidgetHost()), |
| blink::WebInputEvent::kTouchEnd); |
| InputEventAckWaiter scroll_end_at_parent( |
| rwhv_parent->GetRenderWidgetHost(), |
| blink::WebInputEvent::kGestureScrollEnd); |
| BadInputEventObserver no_scroll_bubbling_to_root( |
| static_cast<RenderWidgetHostImpl*>(root_rwhv->GetRenderWidgetHost()), |
| blink::WebInputEvent::kGestureScrollBegin); |
| |
| MainThreadFrameObserver synchronize_threads( |
| rwhv_nested->GetRenderWidgetHost()); |
| synchronize_threads.Wait(); |
| |
| #if defined(USE_AURA) |
| // Allow the scroll gesture through under mash. |
| base::Optional<SystemEventRewriter::ScopedAllow> maybe_scoped_allow_events; |
| if (features::IsSingleProcessMash()) { |
| maybe_scoped_allow_events.emplace(&event_rewriter_); |
| } |
| #endif |
| |
| SyntheticSmoothScrollGestureParams params; |
| params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT; |
| const gfx::PointF location_in_widget(25, 25); |
| const gfx::PointF location_in_root = |
| rwhv_nested->TransformPointToRootCoordSpaceF(location_in_widget); |
| params.anchor = location_in_root; |
| params.distances.push_back(gfx::Vector2d(0, 100)); |
| params.prevent_fling = false; |
| RenderWidgetHostImpl* root_widget_host = |
| static_cast<RenderWidgetHostImpl*>(root_rwhv->GetRenderWidgetHost()); |
| auto dont_care_on_complete = |
| base::BindOnce([](SyntheticGesture::Result result) {}); |
| root_widget_host->QueueSyntheticGesture( |
| std::make_unique<SyntheticSmoothScrollGesture>(params), |
| std::move(dont_care_on_complete)); |
| |
| outgoing_touch_end_waiter.Wait(); |
| |
| // We are now waiting for the touch events to be acked from the nested OOPIF |
| // which will result in a scroll gesture that will bubble from the nested |
| // frame. Meanwhile, we start a new gesture in the main frame. |
| |
| const gfx::PointF point_in_root(1, 1); |
| blink::WebTouchEvent touch_event( |
| blink::WebInputEvent::kTouchStart, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| touch_event.touches_length = 1; |
| touch_event.touches[0].state = blink::WebTouchPoint::kStatePressed; |
| SetWebEventPositions(&touch_event.touches[0], point_in_root, root_rwhv); |
| touch_event.unique_touch_event_id = 1; |
| InputEventAckWaiter root_touch_waiter(root_rwhv->GetRenderWidgetHost(), |
| blink::WebInputEvent::kTouchStart); |
| router->RouteTouchEvent(root_rwhv, &touch_event, |
| ui::LatencyInfo(ui::SourceEventType::TOUCH)); |
| root_touch_waiter.Wait(); |
| |
| blink::WebGestureEvent gesture_event( |
| blink::WebInputEvent::kGestureTapDown, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests(), |
| blink::kWebGestureDeviceTouchscreen); |
| gesture_event.unique_touch_event_id = touch_event.unique_touch_event_id; |
| router->RouteGestureEvent(root_rwhv, &gesture_event, |
| ui::LatencyInfo(ui::SourceEventType::TOUCH)); |
| |
| scroll_end_at_parent.Wait(); |
| // By this point, the parent frame attempted to bubble scroll to the main |
| // frame. |no_scroll_bubbling_to_root| checks that the bubbling stopped at |
| // the parent. |
| } |
| |
| class SitePerProcessEmulatedTouchBrowserTest |
| : public SitePerProcessHitTestBrowserTest { |
| public: |
| enum TestType { |
| ScrollBubbling, |
| PinchGoesToMainFrame, |
| TouchActionBubbling, |
| ShowPressHasTouchID |
| }; |
| |
| ~SitePerProcessEmulatedTouchBrowserTest() override {} |
| |
| void RunTest(TestType test_type) { |
| std::string url; |
| if (test_type == TouchActionBubbling) |
| url = "/frame_tree/page_with_pany_frame.html"; |
| else |
| url = "/frame_tree/page_with_positioned_frame.html"; |
| GURL main_url(embedded_test_server()->GetURL(url)); |
| ASSERT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| // It is safe to obtain the root frame tree node here, as it doesn't change. |
| FrameTreeNode* root = web_contents()->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| FrameTreeNode* iframe_node = root->child_at(0); |
| GURL site_url(embedded_test_server()->GetURL("baz.com", "/title1.html")); |
| EXPECT_EQ(site_url, iframe_node->current_url()); |
| |
| RenderWidgetHostViewBase* root_rwhv = |
| static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| RenderWidgetHostViewBase* child_rwhv = |
| static_cast<RenderWidgetHostViewBase*>(iframe_node->current_frame_host() |
| ->GetRenderWidgetHost() |
| ->GetView()); |
| |
| RenderWidgetHostInputEventRouter* router = |
| static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetInputEventRouter(); |
| |
| WaitForHitTestDataOrChildSurfaceReady(iframe_node->current_frame_host()); |
| |
| auto expect_gesture_with_position = base::BindRepeating( |
| [](blink::WebInputEvent::Type expected_type, |
| const gfx::Point& expected_position, content::InputEventAckSource, |
| content::InputEventAckState, const blink::WebInputEvent& event) { |
| if (event.GetType() != expected_type) |
| return false; |
| |
| const blink::WebGestureEvent& gesture_event = |
| static_cast<const blink::WebGestureEvent&>(event); |
| EXPECT_NEAR(expected_position.x(), gesture_event.PositionInWidget().x, |
| kHitTestTolerance); |
| EXPECT_NEAR(expected_position.y(), gesture_event.PositionInWidget().y, |
| kHitTestTolerance); |
| EXPECT_EQ(blink::kWebGestureDeviceTouchscreen, |
| gesture_event.SourceDevice()); |
| // We expect all gesture events to have non-zero ids otherwise they |
| // can force hit-testing in RenderWidgetHostInputEventRouter even |
| // when it's unnecessary. |
| EXPECT_NE(0U, gesture_event.unique_touch_event_id); |
| return true; |
| }); |
| |
| blink::WebInputEvent::Type expected_gesture_type; |
| switch (test_type) { |
| case ScrollBubbling: |
| case TouchActionBubbling: |
| expected_gesture_type = blink::WebInputEvent::kGestureScrollBegin; |
| break; |
| case PinchGoesToMainFrame: |
| expected_gesture_type = blink::WebInputEvent::kGesturePinchBegin; |
| break; |
| case ShowPressHasTouchID: |
| expected_gesture_type = blink::WebInputEvent::kGestureShowPress; |
| break; |
| default: |
| ASSERT_TRUE(false); |
| } |
| |
| #if defined(OS_WIN) |
| { |
| gfx::Rect view_bounds = root_rwhv->GetViewBounds(); |
| LOG(ERROR) << "Root view bounds = (" << view_bounds.x() << "," |
| << view_bounds.y() << ") " << view_bounds.width() << " x " |
| << view_bounds.height(); |
| } |
| #endif |
| |
| gfx::Point position_in_child(5, 5); |
| InputEventAckWaiter child_gesture_event_observer( |
| child_rwhv->GetRenderWidgetHost(), |
| base::BindRepeating(expect_gesture_with_position, expected_gesture_type, |
| position_in_child)); |
| |
| gfx::Point position_in_root = |
| child_rwhv->TransformPointToRootCoordSpace(position_in_child); |
| InputEventAckWaiter root_gesture_event_observer( |
| root_rwhv->GetRenderWidgetHost(), |
| base::BindRepeating(expect_gesture_with_position, expected_gesture_type, |
| position_in_root)); |
| |
| // Enable touch emulation. |
| auto* touch_emulator = router->GetTouchEmulator(); |
| ASSERT_TRUE(touch_emulator); |
| touch_emulator->Enable(TouchEmulator::Mode::kEmulatingTouchFromMouse, |
| ui::GestureProviderConfigType::CURRENT_PLATFORM); |
| |
| // Create mouse events to emulate touch scroll. Since the page has no touch |
| // handlers, these events will be converted into a gesture scroll sequence. |
| base::TimeTicks simulated_event_time = ui::EventTimeForNow(); |
| base::TimeDelta simulated_event_time_delta = |
| base::TimeDelta::FromMilliseconds(100); |
| blink::WebMouseEvent mouse_move_event = |
| SyntheticWebMouseEventBuilder::Build(blink::WebInputEvent::kMouseMove, |
| position_in_root.x(), |
| position_in_root.y(), 0); |
| mouse_move_event.SetTimeStamp(simulated_event_time); |
| |
| int mouse_modifier = (test_type == PinchGoesToMainFrame) |
| ? blink::WebInputEvent::kShiftKey |
| : 0; |
| mouse_modifier |= blink::WebInputEvent::kLeftButtonDown; |
| blink::WebMouseEvent mouse_down_event = |
| SyntheticWebMouseEventBuilder::Build( |
| blink::WebInputEvent::kMouseDown, position_in_root.x(), |
| position_in_root.y(), mouse_modifier); |
| mouse_down_event.button = blink::WebMouseEvent::Button::kLeft; |
| simulated_event_time += simulated_event_time_delta; |
| mouse_down_event.SetTimeStamp(simulated_event_time); |
| |
| blink::WebMouseEvent mouse_drag_event = |
| SyntheticWebMouseEventBuilder::Build( |
| blink::WebInputEvent::kMouseMove, position_in_root.x(), |
| position_in_root.y() + 20, mouse_modifier); |
| simulated_event_time += simulated_event_time_delta; |
| mouse_drag_event.SetTimeStamp(simulated_event_time); |
| mouse_drag_event.button = blink::WebMouseEvent::Button::kLeft; |
| |
| blink::WebMouseEvent mouse_up_event = SyntheticWebMouseEventBuilder::Build( |
| blink::WebInputEvent::kMouseUp, position_in_root.x(), |
| position_in_root.y() + 20, mouse_modifier); |
| mouse_up_event.button = blink::WebMouseEvent::Button::kLeft; |
| simulated_event_time += simulated_event_time_delta; |
| mouse_up_event.SetTimeStamp(simulated_event_time); |
| |
| // Send mouse events and wait for GesturePinchBegin. |
| router->RouteMouseEvent(root_rwhv, &mouse_move_event, ui::LatencyInfo()); |
| router->RouteMouseEvent(root_rwhv, &mouse_down_event, ui::LatencyInfo()); |
| if (test_type == ShowPressHasTouchID) { |
| // Wait for child to receive GestureShowPress. If this test fails, it |
| // will either DCHECK or time out. |
| child_gesture_event_observer.Wait(); |
| return; |
| } |
| router->RouteMouseEvent(root_rwhv, &mouse_drag_event, ui::LatencyInfo()); |
| router->RouteMouseEvent(root_rwhv, &mouse_up_event, ui::LatencyInfo()); |
| |
| if (test_type == ScrollBubbling || test_type == TouchActionBubbling) { |
| // Verify child receives GestureScrollBegin. |
| child_gesture_event_observer.Wait(); |
| } |
| |
| // Verify the root receives the GesturePinchBegin or GestureScrollBegin, |
| // depending on |test_type|. |
| root_gesture_event_observer.Wait(); |
| |
| // Shut down. |
| touch_emulator->Disable(); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessEmulatedTouchBrowserTest, |
| EmulatedTouchShowPressHasTouchID) { |
| RunTest(ShowPressHasTouchID); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessEmulatedTouchBrowserTest, |
| EmulatedTouchScrollBubbles) { |
| RunTest(ScrollBubbling); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessEmulatedTouchBrowserTest, |
| EmulatedTouchPinchGoesToMainFrame) { |
| RunTest(PinchGoesToMainFrame); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessEmulatedTouchBrowserTest, |
| EmulatedGestureScrollBubbles) { |
| RunTest(TouchActionBubbling); |
| } |
| |
| // Regression test for https://crbug.com/851644. The test passes as long as it |
| // doesn't crash. |
| // Touch action ack timeout is enabled on Android only. |
| #if defined(OS_ANDROID) |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| TouchActionAckTimeout) { |
| GURL main_url( |
| embedded_test_server()->GetURL("/frame_tree/page_with_janky_frame.html")); |
| ASSERT_TRUE(NavigateToURL(shell(), main_url)); |
| FrameTreeNode* root = web_contents()->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| GURL frame_url(embedded_test_server()->GetURL( |
| "baz.com", "/page_with_touch_start_janking_main_thread.html")); |
| auto* child_frame_host = root->child_at(0)->current_frame_host(); |
| |
| RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| RenderWidgetHostViewChildFrame* rwhv_child = |
| static_cast<RenderWidgetHostViewChildFrame*>( |
| child_frame_host->GetRenderWidgetHost()->GetView()); |
| |
| WaitForHitTestDataOrChildSurfaceReady(child_frame_host); |
| |
| // Compute the point so that the gesture event can target the child frame. |
| const gfx::Rect root_bounds = rwhv_root->GetViewBounds(); |
| const gfx::Rect child_bounds = rwhv_child->GetViewBounds(); |
| RenderFrameSubmissionObserver render_frame_submission_observer( |
| shell()->web_contents()); |
| const float page_scale_factor = |
| render_frame_submission_observer.LastRenderFrameMetadata() |
| .page_scale_factor; |
| const gfx::PointF point_in_child( |
| (child_bounds.x() - root_bounds.x() + 25) * page_scale_factor, |
| (child_bounds.y() - root_bounds.y() + 25) * page_scale_factor); |
| |
| SyntheticSmoothScrollGestureParams params; |
| params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT; |
| params.anchor = gfx::PointF(point_in_child.x(), point_in_child.y()); |
| params.distances.push_back(gfx::Vector2dF(0, -10)); |
| // The JS jank from the "page_with_touch_start_janking_main_thread.html" |
| // causes the touch ack timeout. Set the speed high so that the gesture can be |
| // completed quickly and so does this test. |
| params.speed_in_pixels_s = 100000; |
| std::unique_ptr<SyntheticSmoothScrollGesture> gesture( |
| new SyntheticSmoothScrollGesture(params)); |
| |
| InputEventAckWaiter ack_observer( |
| child_frame_host->GetRenderWidgetHost(), |
| base::BindRepeating([](content::InputEventAckSource source, |
| content::InputEventAckState state, |
| const blink::WebInputEvent& event) { |
| return event.GetType() == blink::WebGestureEvent::kGestureScrollEnd; |
| })); |
| ack_observer.Reset(); |
| |
| RenderWidgetHostImpl* render_widget_host = |
| root->current_frame_host()->GetRenderWidgetHost(); |
| render_widget_host->QueueSyntheticGesture( |
| std::move(gesture), base::BindOnce([](SyntheticGesture::Result result) { |
| EXPECT_EQ(SyntheticGesture::GESTURE_FINISHED, result); |
| })); |
| ack_observer.Wait(); |
| } |
| #endif // defined(OS_ANDROID) |
| |
| #if defined(USE_AURA) || defined(OS_ANDROID) |
| |
| // When unconsumed scrolls in a child bubble to the root and start an |
| // overscroll gesture, the subsequent gesture scroll update events should be |
| // consumed by the root. The child should not be able to scroll during the |
| // overscroll gesture. |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| RootConsumesScrollDuringOverscrollGesture) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b)")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| RenderFrameSubmissionObserver render_frame_submission_observer( |
| shell()->web_contents()); |
| |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree() |
| ->root(); |
| RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| FrameTreeNode* child_node = root->child_at(0); |
| |
| #if defined(USE_AURA) |
| // The child must be horizontally scrollable. |
| GURL child_url(embedded_test_server()->GetURL("b.com", "/wide_page.html")); |
| #elif defined(OS_ANDROID) |
| // The child must be vertically scrollable. |
| GURL child_url(embedded_test_server()->GetURL("b.com", "/tall_page.html")); |
| #endif |
| NavigateFrameToURL(child_node, child_url); |
| |
| EXPECT_EQ( |
| " Site A ------------ proxies for B\n" |
| " +--Site B ------- proxies for A\n" |
| "Where A = http://a.com/\n" |
| " B = http://b.com/", |
| DepictFrameTree(root)); |
| |
| RenderWidgetHostViewChildFrame* rwhv_child = |
| static_cast<RenderWidgetHostViewChildFrame*>( |
| child_node->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host()); |
| |
| ASSERT_TRUE(rwhv_root->IsScrollOffsetAtTop()); |
| ASSERT_TRUE(rwhv_child->IsScrollOffsetAtTop()); |
| |
| RenderWidgetHostInputEventRouter* router = |
| static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetInputEventRouter(); |
| |
| { |
| // Set up the RenderWidgetHostInputEventRouter to send the gesture stream |
| // to the child. |
| const gfx::Rect root_bounds = rwhv_root->GetViewBounds(); |
| const gfx::Rect child_bounds = rwhv_child->GetViewBounds(); |
| const float page_scale_factor = |
| render_frame_submission_observer.LastRenderFrameMetadata() |
| .page_scale_factor; |
| const gfx::PointF point_in_root( |
| (child_bounds.x() - root_bounds.x() + 10) * page_scale_factor, |
| (child_bounds.y() - root_bounds.y() + 10) * page_scale_factor); |
| |
| blink::WebTouchEvent touch_event( |
| blink::WebInputEvent::kTouchStart, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| touch_event.touches_length = 1; |
| touch_event.touches[0].state = blink::WebTouchPoint::kStatePressed; |
| SetWebEventPositions(&touch_event.touches[0], point_in_root, rwhv_root); |
| touch_event.unique_touch_event_id = 1; |
| InputEventAckWaiter waiter(rwhv_child->GetRenderWidgetHost(), |
| blink::WebInputEvent::kTouchStart); |
| router->RouteTouchEvent(rwhv_root, &touch_event, |
| ui::LatencyInfo(ui::SourceEventType::TOUCH)); |
| // With async hit testing, make sure the target for the initial TouchStart |
| // is resolved before sending the rest of the stream. |
| waiter.Wait(); |
| |
| blink::WebGestureEvent gesture_event( |
| blink::WebInputEvent::kGestureTapDown, |
| blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests(), |
| blink::kWebGestureDeviceTouchscreen); |
| gesture_event.unique_touch_event_id = touch_event.unique_touch_event_id; |
| router->RouteGestureEvent(rwhv_root, &gesture_event, |
| ui::LatencyInfo(ui::SourceEventType::TOUCH)); |
| } |
| |
| #if defined(USE_AURA) |
| RenderWidgetHostViewAura* rwhva = |
| static_cast<RenderWidgetHostViewAura*>(rwhv_root); |
| std::unique_ptr<MockOverscrollControllerDelegateAura> |
| mock_overscroll_delegate = |
| std::make_unique<MockOverscrollControllerDelegateAura>(rwhva); |
| rwhva->overscroll_controller()->set_delegate(mock_overscroll_delegate.get()); |
| MockOverscrollObserver* mock_overscroll_observer = |
| mock_overscroll_delegate.get(); |
| #elif defined(OS_ANDROID) |
| RenderWidgetHostViewAndroid* rwhv_android = |
| static_cast<RenderWidgetHostViewAndroid*>(rwhv_root); |
| std::unique_ptr<MockOverscrollRefreshHandlerAndroid> mock_overscroll_handler = |
| std::make_unique<MockOverscrollRefreshHandlerAndroid>(); |
| rwhv_android->SetOverscrollControllerForTesting( |
| mock_overscroll_handler.get()); |
| MockOverscrollObserver* mock_overscroll_observer = |
| mock_overscroll_handler.get(); |
| #endif // defined(USE_AURA) |
| |
| InputEventAckWaiter gesture_begin_observer_child( |
| child_node->current_frame_host()->GetRenderWidgetHost(), |
| blink::WebInputEvent::kGestureScrollBegin); |
| InputEventAckWaiter gesture_end_observer_child( |
| child_node->current_frame_host()->GetRenderWidgetHost(), |
| blink::WebInputEvent::kGestureScrollEnd); |
| |
| #if defined(USE_AURA) |
| const float overscroll_threshold = OverscrollConfig::GetThreshold( |
| OverscrollConfig::Threshold::kStartTouchscreen); |
| #elif defined(OS_ANDROID) |
| const float overscroll_threshold = 0.f; |
| #endif |
| |
| // First we need our scroll to initiate an overscroll gesture in the root |
| // via unconsumed scrolls in the child. |
| blink::WebGestureEvent gesture_scroll_begin( |
| blink::WebGestureEvent::kGestureScrollBegin, |
| blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests(), |
| blink::kWebGestureDeviceTouchscreen); |
| gesture_scroll_begin.unique_touch_event_id = 1; |
| gesture_scroll_begin.data.scroll_begin.delta_hint_units = |
| blink::WebGestureEvent::ScrollUnits::kPrecisePixels; |
| gesture_scroll_begin.data.scroll_begin.delta_x_hint = 0.f; |
| gesture_scroll_begin.data.scroll_begin.delta_y_hint = 0.f; |
| #if defined(USE_AURA) |
| // For aura, we scroll horizontally to activate an overscroll navigation. |
| gesture_scroll_begin.data.scroll_begin.delta_x_hint = |
| overscroll_threshold + 1; |
| #elif defined(OS_ANDROID) |
| // For android, we scroll vertically to activate pull-to-refresh. |
| gesture_scroll_begin.data.scroll_begin.delta_y_hint = |
| overscroll_threshold + 1; |
| #endif |
| router->RouteGestureEvent(rwhv_root, &gesture_scroll_begin, |
| ui::LatencyInfo(ui::SourceEventType::TOUCH)); |
| |
| // Make sure the child is indeed receiving the gesture stream. |
| gesture_begin_observer_child.Wait(); |
| |
| blink::WebGestureEvent gesture_scroll_update( |
| blink::WebGestureEvent::kGestureScrollUpdate, |
| blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests(), |
| blink::kWebGestureDeviceTouchscreen); |
| gesture_scroll_update.unique_touch_event_id = 1; |
| gesture_scroll_update.data.scroll_update.delta_units = |
| blink::WebGestureEvent::ScrollUnits::kPrecisePixels; |
| gesture_scroll_update.data.scroll_update.delta_x = 0.f; |
| gesture_scroll_update.data.scroll_update.delta_y = 0.f; |
| #if defined(USE_AURA) |
| float* delta = &gesture_scroll_update.data.scroll_update.delta_x; |
| #elif defined(OS_ANDROID) |
| float* delta = &gesture_scroll_update.data.scroll_update.delta_y; |
| #endif |
| *delta = overscroll_threshold + 1; |
| mock_overscroll_observer->Reset(); |
| // This will bring us into an overscroll gesture. |
| router->RouteGestureEvent(rwhv_root, &gesture_scroll_update, |
| ui::LatencyInfo(ui::SourceEventType::TOUCH)); |
| // Note that in addition to verifying that we get the overscroll update, it |
| // is necessary to wait before sending the next event to prevent our multiple |
| // GestureScrollUpdates from being coalesced. |
| mock_overscroll_observer->WaitForUpdate(); |
| |
| // This scroll is in the same direction and so it will contribute to the |
| // overscroll. |
| *delta = 10.0f; |
| mock_overscroll_observer->Reset(); |
| router->RouteGestureEvent(rwhv_root, &gesture_scroll_update, |
| ui::LatencyInfo(ui::SourceEventType::TOUCH)); |
| mock_overscroll_observer->WaitForUpdate(); |
| |
| // Now we reverse direction. The child could scroll in this direction, but |
| // since we're in an overscroll gesture, the root should consume it. |
| *delta = -5.0f; |
| mock_overscroll_observer->Reset(); |
| router->RouteGestureEvent(rwhv_root, &gesture_scroll_update, |
| ui::LatencyInfo(ui::SourceEventType::TOUCH)); |
| mock_overscroll_observer->WaitForUpdate(); |
| |
| blink::WebGestureEvent gesture_scroll_end( |
| blink::WebGestureEvent::kGestureScrollEnd, |
| blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests(), |
| blink::kWebGestureDeviceTouchscreen); |
| gesture_scroll_end.unique_touch_event_id = 1; |
| gesture_scroll_end.data.scroll_end.delta_units = |
| blink::WebGestureEvent::ScrollUnits::kPrecisePixels; |
| mock_overscroll_observer->Reset(); |
| router->RouteGestureEvent(rwhv_root, &gesture_scroll_end, |
| ui::LatencyInfo(ui::SourceEventType::TOUCH)); |
| mock_overscroll_observer->WaitForEnd(); |
| |
| // Ensure that the method of providing the child's scroll events to the root |
| // does not leave the child in an invalid state. |
| gesture_end_observer_child.Wait(); |
| } |
| #endif // defined(USE_AURA) || defined(OS_ANDROID) |
| |
| // Test that an ET_SCROLL event sent to an out-of-process iframe correctly |
| // results in a scroll. This is only handled by RenderWidgetHostViewAura |
| // and is needed for trackpad scrolling on Chromebooks. |
| #if defined(USE_AURA) |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, ScrollEventToOOPIF) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "/frame_tree/page_with_positioned_frame.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| // It is safe to obtain the root frame tree node here, as it doesn't change. |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree() |
| ->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| FrameTreeNode* child_node = root->child_at(0); |
| GURL site_url(embedded_test_server()->GetURL("baz.com", "/title1.html")); |
| EXPECT_EQ(site_url, child_node->current_url()); |
| EXPECT_NE(shell()->web_contents()->GetSiteInstance(), |
| child_node->current_frame_host()->GetSiteInstance()); |
| |
| RenderWidgetHostViewAura* rwhv_parent = |
| static_cast<RenderWidgetHostViewAura*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host()); |
| |
| // Create listener for input events. |
| TestInputEventObserver child_frame_monitor( |
| child_node->current_frame_host()->GetRenderWidgetHost()); |
| |
| // Send a ui::ScrollEvent that will hit test to the child frame. |
| InputEventAckWaiter waiter( |
| child_node->current_frame_host()->GetRenderWidgetHost(), |
| blink::WebInputEvent::kMouseWheel); |
| ui::ScrollEvent scroll_event(ui::ET_SCROLL, gfx::Point(75, 75), |
| ui::EventTimeForNow(), ui::EF_NONE, 0, |
| 10, // Offsets |
| 0, 10, // Offset ordinals |
| 2); |
| UpdateEventRootLocation(&scroll_event, rwhv_parent); |
| rwhv_parent->OnScrollEvent(&scroll_event); |
| waiter.Wait(); |
| |
| // Verify that this a mouse wheel event was sent to the child frame renderer. |
| EXPECT_TRUE(child_frame_monitor.EventWasReceived()); |
| EXPECT_TRUE(base::ContainsValue(child_frame_monitor.events_received(), |
| blink::WebInputEvent::kMouseWheel)); |
| } |
| |
| // Tests that touching an OOPIF editable element correctly resizes the |
| // viewport and scrolls the element into view so that the element is not |
| // occluded by the on screen keyboard (https://crbug.com/927483) |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| ScrollOOPIFEditableElement) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "/frame_tree/oopif_form_scroll_main.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| // It is safe to obtain the root frame tree node here, as it doesn't change. |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree() |
| ->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| // Get the first iframe, which contains the editable element |
| FrameTreeNode* child_node = root->child_at(0); |
| GURL site_url(embedded_test_server()->GetURL( |
| "baz.com", "/frame_tree/page_with_editable_elements.html")); |
| ASSERT_EQ(site_url, child_node->current_url()); |
| |
| // Make sure the child frame is indeed a OOPIF |
| EXPECT_TRUE(child_node->current_frame_host()->IsCrossProcessSubframe()); |
| |
| // Set focus on an element in the OOPIF |
| EXPECT_TRUE(ExecJs(child_node->current_frame_host(), |
| "document.getElementById('oopif_element').focus()")); |
| MainThreadFrameObserver observer( |
| root->current_frame_host()->GetRenderWidgetHost()); |
| observer.Wait(); |
| // We reset the scroll to 0,0 because setting focus on element |
| // will bring it into view |
| ASSERT_TRUE(ExecJs(root->current_frame_host(), |
| base::StringPrintf("window.scrollTo(%d, %d);", 0, 0))); |
| content::RenderFrameSubmissionObserver root_frame_observer(root); |
| gfx::Vector2dF zero_offset; |
| root_frame_observer.WaitForScrollOffset(zero_offset); |
| |
| RenderWidgetHostViewChildFrame* child_render_widget_host_view_child_frame = |
| static_cast<RenderWidgetHostViewChildFrame*>( |
| child_node->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| RenderWidgetHostViewAura* parent_render_widget_host_aura = |
| static_cast<RenderWidgetHostViewAura*>( |
| child_render_widget_host_view_child_frame->GetRootView()); |
| |
| gfx::Size original_viewport_size = |
| parent_render_widget_host_aura->GetVisibleViewportSize(); |
| int htmlScrollHeight = EvalJs(root->current_frame_host(), |
| "document.documentElement.scrollHeight") |
| .ExtractInt(); |
| const int inset_height = 200; |
| parent_render_widget_host_aura->SetLastPointerType( |
| ui::EventPointerType::POINTER_TYPE_TOUCH); |
| parent_render_widget_host_aura->FocusedNodeTouched(true); |
| parent_render_widget_host_aura->SetInsets(gfx::Insets(0, 0, inset_height, 0)); |
| |
| // After focus on editable element, we expect element to be scrolled |
| // into view. Verify that the scroll offset on the root document |
| // matches a value that would make the OOPIF element visible. |
| // When a page is scrolled to the bottom |
| // window.scrollY == htmlScrollHeight - viewportHeight; |
| // Verify that setting insets scrolled to the bottom to make |
| // editable element visible. |
| gfx::Vector2dF final_root_offset( |
| 0, htmlScrollHeight - (original_viewport_size.height() - inset_height)); |
| root_frame_observer.WaitForScrollOffset(final_root_offset); |
| EXPECT_EQ(EvalJs(root->current_frame_host(), "window.scrollY").ExtractInt(), |
| htmlScrollHeight - original_viewport_size.height()); |
| // Viewport should be resized by keyboard height |
| EXPECT_NE(parent_render_widget_host_aura->GetVisibleViewportSize(), |
| original_viewport_size); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| InputEventRouterWheelCoalesceTest) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "/frame_tree/page_with_positioned_frame.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| // It is safe to obtain the root frame tree node here, as it doesn't change. |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree() |
| ->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| FrameTreeNode* child_node = root->child_at(0); |
| GURL site_url(embedded_test_server()->GetURL("baz.com", "/title1.html")); |
| EXPECT_EQ(site_url, child_node->current_url()); |
| EXPECT_NE(shell()->web_contents()->GetSiteInstance(), |
| child_node->current_frame_host()->GetSiteInstance()); |
| |
| RenderWidgetHostViewAura* rwhv_parent = |
| static_cast<RenderWidgetHostViewAura*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host()); |
| |
| RenderWidgetHostInputEventRouter* router = |
| web_contents()->GetInputEventRouter(); |
| |
| // Create listener for input events. |
| TestInputEventObserver child_frame_monitor( |
| child_node->current_frame_host()->GetRenderWidgetHost()); |
| InputEventAckWaiter waiter( |
| child_node->current_frame_host()->GetRenderWidgetHost(), |
| blink::WebInputEvent::kMouseWheel); |
| |
| // Send a mouse wheel event to child. |
| blink::WebMouseWheelEvent wheel_event( |
| blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| SetWebEventPositions(&wheel_event, gfx::Point(75, 75), rwhv_parent); |
| wheel_event.delta_x = 10; |
| wheel_event.delta_y = 20; |
| wheel_event.phase = blink::WebMouseWheelEvent::kPhaseBegan; |
| router->RouteMouseWheelEvent(rwhv_parent, &wheel_event, ui::LatencyInfo()); |
| |
| // Send more mouse wheel events to the child. Since we are waiting for the |
| // async targeting on the first event, these new mouse wheel events should |
| // be coalesced properly. |
| blink::WebMouseWheelEvent wheel_event1( |
| blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| SetWebEventPositions(&wheel_event1, gfx::Point(70, 70), rwhv_parent); |
| wheel_event1.delta_x = 12; |
| wheel_event1.delta_y = 22; |
| wheel_event1.phase = blink::WebMouseWheelEvent::kPhaseChanged; |
| router->RouteMouseWheelEvent(rwhv_parent, &wheel_event1, ui::LatencyInfo()); |
| |
| blink::WebMouseWheelEvent wheel_event2( |
| blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| SetWebEventPositions(&wheel_event2, gfx::Point(65, 65), rwhv_parent); |
| wheel_event2.delta_x = 14; |
| wheel_event2.delta_y = 24; |
| wheel_event2.phase = blink::WebMouseWheelEvent::kPhaseChanged; |
| router->RouteMouseWheelEvent(rwhv_parent, &wheel_event2, ui::LatencyInfo()); |
| |
| // Since we are targeting child, event dispatch should not happen |
| // synchronously. Validate that the expected target does not receive the |
| // event immediately. |
| // When V2 surface layer hit testing is enabled, we expect to do synchronous |
| // event targeting on a child under some circumstances, so we expect the event |
| // immediately dispatched to the child. |
| if (!features::IsVizHitTestingSurfaceLayerEnabled()) |
| EXPECT_FALSE(child_frame_monitor.EventWasReceived()); |
| |
| waiter.Wait(); |
| EXPECT_TRUE(child_frame_monitor.EventWasReceived()); |
| EXPECT_EQ(child_frame_monitor.EventType(), blink::WebInputEvent::kMouseWheel); |
| |
| // Check if the two mouse-wheel update events are coalesced correctly. |
| const auto& gesture_event = |
| static_cast<const blink::WebGestureEvent&>(child_frame_monitor.event()); |
| EXPECT_EQ(26 /* wheel_event1.delta_x + wheel_event2.delta_x */, |
| gesture_event.data.scroll_update.delta_x); |
| EXPECT_EQ(46 /* wheel_event1.delta_y + wheel_event2.delta_y */, |
| gesture_event.data.scroll_update.delta_y); |
| } |
| #endif // defined(USE_AURA) |
| |
| // Test that mouse events are being routed to the correct RenderWidgetHostView |
| // based on coordinates. |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, SurfaceHitTestTest) { |
| SurfaceHitTestTestHelper(shell(), embedded_test_server()); |
| } |
| |
| // Same test as above, but runs in high-dpi mode. |
| // NOTE: This has to be renamed from SurfaceHitTestTest to |
| // HighDPISurfaceHitTestTest. Otherwise MAYBE_SurfaceHitTestTest gets #defined |
| // twice. |
| #if defined(OS_ANDROID) || defined(OS_WIN) |
| // High DPI browser tests are not needed on Android, and confuse some of the |
| // coordinate calculations. Android uses fixed device scale factor. |
| // Windows is disabled because of https://crbug.com/545547. |
| #define MAYBE_HighDPISurfaceHitTestTest DISABLED_HighDPISurfaceHitTestTest |
| #else |
| #define MAYBE_HighDPISurfaceHitTestTest HighDPISurfaceHitTestTest |
| #endif |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest, |
| MAYBE_HighDPISurfaceHitTestTest) { |
| SurfaceHitTestTestHelper(shell(), embedded_test_server()); |
| } |
| |
| // Test that mouse events are being routed to the correct RenderWidgetHostView |
| // when there are nested out-of-process iframes. |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| NestedSurfaceHitTestTest) { |
| NestedSurfaceHitTestTestHelper(shell(), embedded_test_server()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest, |
| NestedSurfaceHitTestTest) { |
| NestedSurfaceHitTestTestHelper(shell(), embedded_test_server()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| NonFlatTransformedSurfaceHitTestTest) { |
| NonFlatTransformedSurfaceHitTestHelper(shell(), embedded_test_server()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest, |
| NonFlatTransformedSurfaceHitTestTest) { |
| NonFlatTransformedSurfaceHitTestHelper(shell(), embedded_test_server()); |
| } |
| |
| // TODO(kenrb): Running this test on Android bots has slight discrepancies in |
| // transformed event coordinates when we do manual calculation of expected |
| // values. We can't rely on browser side transformation because it is broken |
| // for perspective transforms. See https://crbug.com/854247. |
| #if defined(OS_ANDROID) |
| #define MAYBE_PerspectiveTransformedSurfaceHitTestTest \ |
| DISABLED_PerspectiveTransformedSurfaceHitTestTest |
| #else |
| #define MAYBE_PerspectiveTransformedSurfaceHitTestTest \ |
| PerspectiveTransformedSurfaceHitTestTest |
| #endif |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| MAYBE_PerspectiveTransformedSurfaceHitTestTest) { |
| PerspectiveTransformedSurfaceHitTestHelper(shell(), embedded_test_server()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest, |
| MAYBE_PerspectiveTransformedSurfaceHitTestTest) { |
| PerspectiveTransformedSurfaceHitTestHelper(shell(), embedded_test_server()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest, |
| OverlapSurfaceHitTestTest) { |
| OverlapSurfaceHitTestHelper(shell(), embedded_test_server()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| OverlapSurfaceHitTestTest) { |
| OverlapSurfaceHitTestHelper(shell(), embedded_test_server()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| HitTestLayerSquashing) { |
| HitTestLayerSquashing(shell(), embedded_test_server()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest |