| // 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 RenderWidgetHostMouseEventMonitor { |
| public: |
| explicit RenderWidgetHostMouseEventMonitor(RenderWidgetHost* host) |
| : host_(host), event_received_(false) { |
| mouse_callback_ = |
| base::Bind(&RenderWidgetHostMouseEventMonitor::MouseEventCallback, |
| base::Unretained(this)); |
| host_->AddMouseEventCallback(mouse_callback_); |
| } |
| ~RenderWidgetHostMouseEventMonitor() { |
| host_->RemoveMouseEventCallback(mouse_callback_); |
| } |
| bool EventWasReceived() const { return event_received_; } |
| void ResetEventReceived() { event_received_ = false; } |
| const blink::WebMouseEvent& event() const { return event_; } |
| |
| private: |
| bool MouseEventCallback(const blink::WebMouseEvent& event) { |
| event_received_ = true; |
| event_ = event; |
| return false; |
| } |
| RenderWidgetHost::MouseEventCallback mouse_callback_; |
| RenderWidgetHost* host_; |
| bool event_received_; |
| blink::WebMouseEvent event_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostMouseEventMonitor); |
| }; |
| |
| 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)); |
| } |
| |
| 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, |
| HitTestLayerSquashing) { |
| HitTestLayerSquashing(shell(), embedded_test_server()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, HitTestWatermark) { |
| HitTestWatermark(shell(), embedded_test_server()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest, |
| HitTestWatermark) { |
| HitTestWatermark(shell(), embedded_test_server()); |
| } |
| |
| #if defined(USE_AURA) |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, RootWindowTransform) { |
| HitTestRootWindowTransform(shell(), embedded_test_server()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest, |
| RootWindowTransform) { |
| HitTestRootWindowTransform(shell(), embedded_test_server()); |
| } |
| #endif // defined(USE_AURA) |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| HitTestStaleDataDeletedView) { |
| // Have two iframes to avoid going to short circuit path during the second |
| // targeting. |
| GURL main_url( |
| embedded_test_server()->GetURL("/frame_tree/page_with_two_iframes.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| // It is safe to obtain the root frame tree node here, as it doesn't change. |
| auto* web_contents = static_cast<WebContentsImpl*>(shell()->web_contents()); |
| FrameTreeNode* root = web_contents->GetFrameTree()->root(); |
| ASSERT_EQ(2U, root->child_count()); |
| |
| FrameTreeNode* child_node1 = root->child_at(0); |
| GURL site_url1(embedded_test_server()->GetURL("bar.com", "/title1.html")); |
| EXPECT_EQ(site_url1, child_node1->current_url()); |
| EXPECT_NE(shell()->web_contents()->GetSiteInstance(), |
| child_node1->current_frame_host()->GetSiteInstance()); |
| |
| FrameTreeNode* child_node2 = root->child_at(1); |
| GURL site_url2(embedded_test_server()->GetURL("baz.com", "/title1.html")); |
| EXPECT_EQ(site_url2, child_node2->current_url()); |
| EXPECT_NE(shell()->web_contents()->GetSiteInstance(), |
| child_node2->current_frame_host()->GetSiteInstance()); |
| |
| RenderWidgetHostImpl* root_rwh = static_cast<RenderWidgetHostImpl*>( |
| root->current_frame_host()->GetRenderWidgetHost()); |
| RenderWidgetHostViewBase* rwhv_parent = |
| static_cast<RenderWidgetHostViewBase*>(root_rwh->GetView()); |
| RenderWidgetHostViewBase* rwhv_child2 = |
| static_cast<RenderWidgetHostViewBase*>( |
| child_node2->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| WaitForHitTestDataOrChildSurfaceReady(child_node1->current_frame_host()); |
| WaitForHitTestDataOrChildSurfaceReady(child_node2->current_frame_host()); |
| |
| const gfx::PointF child_location(50, 50); |
| gfx::PointF parent_location = |
| rwhv_child2->TransformPointToRootCoordSpaceF(child_location); |
| // Send a mouse-down at the center of the child2. This should go to the |
| // child2. |
| DispatchMouseEventAndWaitUntilDispatch( |
| web_contents, rwhv_parent, parent_location, rwhv_child2, child_location); |
| |
| // Remove the iframe from the page. Add an infinite loop at the end so that |
| // renderer wouldn't submit updated hit-test data. |
| FrameDeletedObserver delete_observer(child_node2->current_frame_host()); |
| ExecuteScriptAsync( |
| root, |
| "document.body.removeChild(document.getElementsByName('frame2')[0]);" |
| "while(true) {}"); |
| delete_observer.Wait(); |
| EXPECT_EQ(1U, root->child_count()); |
| |
| // The synchronous targeting for the same location should now find the |
| // root-view as the target (and require async-targeting), since child2 has |
| // been removed. We cannot actually attempt to dispatch the event though, |
| // since it would try to do asynchronous targeting by asking the root-view, |
| // whose main-thread is blocked because of the infinite-loop in the injected |
| // javascript above. |
| 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; |
| SetWebEventPositions(&down_event, parent_location, rwhv_parent); |
| auto result = web_contents->GetInputEventRouter()->FindTargetSynchronously( |
| rwhv_parent, down_event); |
| EXPECT_EQ(result.view, rwhv_parent); |
| // When VizHitTestSurfaceLayer is enabled and there is only one child frame, |
| // we can find the target frame and are sure there are no other possible |
| // targets, in this case, we dispatch the event immediately without |
| // asynchronously querying the root-view. |
| if (features::IsVizHitTestingSurfaceLayerEnabled()) |
| EXPECT_FALSE(result.should_query_view); |
| else |
| EXPECT_TRUE(result.should_query_view); |
| EXPECT_EQ(result.target_location.value(), parent_location); |
| } |
| |
| // This test tests that browser process hittesting ignores frames with |
| // pointer-events: none. |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| SurfaceHitTestPointerEventsNoneChanged) { |
| // In /2 hit testing, OOPIFs with pointer-events: none are ignored and no hit |
| // test data is submitted. To make sure we wait enough time until child frame |
| // fully loaded, we add a 1x1 pixel OOPIF for the test to track the process of |
| // /2 hit testing. |
| GURL main_url(embedded_test_server()->GetURL( |
| "/frame_tree/page_with_positioned_frame_pointer-events_none.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(2U, root->child_count()); |
| |
| FrameTreeNode* child_node1 = root->child_at(0); |
| FrameTreeNode* child_node2 = root->child_at(1); |
| GURL site_url(embedded_test_server()->GetURL("bar.com", "/title1.html")); |
| EXPECT_EQ(site_url, child_node2->current_url()); |
| EXPECT_NE(shell()->web_contents()->GetSiteInstance(), |
| child_node2->current_frame_host()->GetSiteInstance()); |
| |
| // Create listeners for mouse events. |
| RenderWidgetHostMouseEventMonitor main_frame_monitor( |
| root->current_frame_host()->GetRenderWidgetHost()); |
| RenderWidgetHostMouseEventMonitor child_frame_monitor( |
| child_node1->current_frame_host()->GetRenderWidgetHost()); |
| |
| RenderWidgetHostInputEventRouter* router = |
| web_contents()->GetInputEventRouter(); |
| |
| RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| WaitForHitTestDataOrChildSurfaceReady(child_node2->current_frame_host()); |
| |
| // Target input event to child1 frame. |
| blink::WebMouseEvent child_event( |
| blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| child_event.button = blink::WebPointerProperties::Button::kLeft; |
| SetWebEventPositions(&child_event, gfx::Point(75, 75), root_view); |
| child_event.click_count = 1; |
| main_frame_monitor.ResetEventReceived(); |
| child_frame_monitor.ResetEventReceived(); |
| InputEventAckWaiter waiter(root->current_frame_host()->GetRenderWidgetHost(), |
| blink::WebInputEvent::kMouseDown); |
| router->RouteMouseEvent(root_view, &child_event, ui::LatencyInfo()); |
| waiter.Wait(); |
| |
| EXPECT_TRUE(main_frame_monitor.EventWasReceived()); |
| EXPECT_NEAR(75, main_frame_monitor.event().PositionInWidget().x, |
| kHitTestTolerance); |
| EXPECT_NEAR(75, main_frame_monitor.event().PositionInWidget().y, |
| kHitTestTolerance); |
| EXPECT_FALSE(child_frame_monitor.EventWasReceived()); |
| |
| // Surface hit test can only learn about pointer-events changes when |
| // submitting compositing frame, so we disable the second half of the test for |
| // surface hit test. |
| if (!features::IsVizHitTestingEnabled()) |
| return; |
| |
| // Remove pointer-events: none property from iframe, also remove child2 to |
| // properly notify the observer the update. |
| // Wait for the confirmation of the deletion so that surface hit test is aware |
| // of the change of pointer-events property. When viz hit testing is enabled, |
| // we do not need to wait. |
| EXPECT_TRUE(ExecuteScript(web_contents(), |
| "document.getElementsByTagName('iframe')[0].style." |
| "pointerEvents = 'auto';\n")); |
| |
| ASSERT_EQ(2U, root->child_count()); |
| |
| { |
| MainThreadFrameObserver observer( |
| root->current_frame_host()->GetRenderWidgetHost()); |
| observer.Wait(); |
| } |
| { |
| MainThreadFrameObserver observer( |
| root->child_at(0)->current_frame_host()->GetRenderWidgetHost()); |
| observer.Wait(); |
| } |
| { |
| MainThreadFrameObserver observer( |
| root->child_at(1)->current_frame_host()->GetRenderWidgetHost()); |
| observer.Wait(); |
| } |
| |
| WaitForHitTestDataOrChildSurfaceReady(child_node1->current_frame_host()); |
| WaitForHitTestDataOrChildSurfaceReady(child_node2->current_frame_host()); |
| main_frame_monitor.ResetEventReceived(); |
| child_frame_monitor.ResetEventReceived(); |
| InputEventAckWaiter child_waiter( |
| child_node1->current_frame_host()->GetRenderWidgetHost(), |
| blink::WebInputEvent::kMouseDown); |
| router->RouteMouseEvent(root_view, &child_event, ui::LatencyInfo()); |
| child_waiter.Wait(); |
| |
| EXPECT_TRUE(child_frame_monitor.EventWasReceived()); |
| EXPECT_NEAR(23, child_frame_monitor.event().PositionInWidget().x, |
| kHitTestTolerance); |
| EXPECT_NEAR(23, child_frame_monitor.event().PositionInWidget().y, |
| kHitTestTolerance); |
| } |
| |
| // This test tests that browser process can successfully hit test on nested |
| // OOPIFs that are partially occluded by main frame elements. |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| HitTestNestedOccludedOOPIF) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "/frame_tree/page_with_nested_frames_and_occluding_div.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* parent = root->child_at(0); |
| |
| GURL site_url(embedded_test_server()->GetURL( |
| "bar.com", "/frame_tree/page_with_positioned_frame.html")); |
| EXPECT_EQ(site_url, parent->current_url()); |
| EXPECT_NE(shell()->web_contents()->GetSiteInstance(), |
| parent->current_frame_host()->GetSiteInstance()); |
| |
| ASSERT_EQ(1U, parent->child_count()); |
| FrameTreeNode* child = parent->child_at(0); |
| GURL child_site_url( |
| embedded_test_server()->GetURL("baz.com", "/title1.html")); |
| EXPECT_EQ(child_site_url, child->current_url()); |
| |
| RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| RenderWidgetHostViewBase* child_view = static_cast<RenderWidgetHostViewBase*>( |
| child->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| WaitForHitTestDataOrChildSurfaceReady(child->current_frame_host()); |
| |
| // Target input event to the overlapping region of main frame's div and child |
| // frame. |
| DispatchMouseEventAndWaitUntilDispatch(web_contents, root_view, |
| gfx::PointF(75, 75), root_view, |
| gfx::PointF(75, 75)); |
| |
| // Target input event to the non overlapping region of child frame. |
| // The div has a bound of (0, 0, 100, 100) with a border-radius of 5px, so |
| // point (99, 99) should not hit test the div but reach the nested child |
| // frame. |
| // The parent frame and child frame both have a default offset of (2, 2) and |
| // child frame's top and left properties are set to be (50, 50), so there is |
| // an offset of (54, 54) in total. |
| DispatchMouseEventAndWaitUntilDispatch(web_contents, root_view, |
| gfx::PointF(99, 99), child_view, |
| gfx::PointF(45, 45)); |
| } |
| |
| // Verify that an event is properly retargeted to the main frame when an |
| // asynchronous hit test to the child frame times out. |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| AsynchronousHitTestChildTimeout) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "/frame_tree/page_with_positioned_busy_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 = web_contents()->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| FrameTreeNode* child_node = root->child_at(0); |
| |
| // Create listeners for mouse events. |
| RenderWidgetHostMouseEventMonitor main_frame_monitor( |
| root->current_frame_host()->GetRenderWidgetHost()); |
| RenderWidgetHostMouseEventMonitor child_frame_monitor( |
| child_node->current_frame_host()->GetRenderWidgetHost()); |
| |
| EXPECT_EQ( |
| " Site A ------------ proxies for B C\n" |
| " +--Site B ------- proxies for A C\n" |
| " +--Site C -- proxies for A B\n" |
| "Where A = http://127.0.0.1/\n" |
| " B = http://baz.com/\n" |
| " C = http://bar.com/", |
| DepictFrameTree(root)); |
| |
| RenderWidgetHostInputEventRouter* router = |
| web_contents()->GetInputEventRouter(); |
| |
| WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host()); |
| |
| // Shorten the timeout for purposes of this test. |
| router->GetRenderWidgetTargeterForTests() |
| ->set_async_hit_test_timeout_delay_for_testing(base::TimeDelta()); |
| |
| RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| EXPECT_TRUE(ExecuteScript(child_node, "lookBusy();")); |
| |
| // Target input event to child frame. It should get delivered to the main |
| // frame instead because the child frame main thread is non-responsive. |
| blink::WebMouseEvent child_event( |
| blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| child_event.button = blink::WebPointerProperties::Button::kLeft; |
| SetWebEventPositions(&child_event, gfx::Point(75, 75), root_view); |
| child_event.click_count = 1; |
| main_frame_monitor.ResetEventReceived(); |
| child_frame_monitor.ResetEventReceived(); |
| RouteMouseEventAndWaitUntilDispatch(router, root_view, root_view, |
| &child_event); |
| |
| EXPECT_TRUE(main_frame_monitor.EventWasReceived()); |
| EXPECT_NEAR(75, main_frame_monitor.event().PositionInWidget().x, |
| kHitTestTolerance); |
| EXPECT_NEAR(75, main_frame_monitor.event().PositionInWidget().y, |
| kHitTestTolerance); |
| EXPECT_FALSE(child_frame_monitor.EventWasReceived()); |
| } |
| |
| // Tooltips aren't used on Android, so no need to compile/run this test in that |
| // case. |
| #if !defined(OS_ANDROID) |
| class TooltipMonitor : public CursorManager::TooltipObserver { |
| public: |
| TooltipMonitor(CursorManager* cursor_manager) : run_loop_(new base::RunLoop) { |
| DCHECK(cursor_manager); |
| cursor_manager->SetTooltipObserverForTesting(this); |
| } |
| ~TooltipMonitor() override {} |
| |
| void Reset() { |
| run_loop_.reset(new base::RunLoop); |
| tooltips_received_.clear(); |
| } |
| |
| void OnSetTooltipTextForView(const RenderWidgetHostViewBase* view, |
| const base::string16& tooltip_text) override { |
| tooltips_received_.push_back(tooltip_text); |
| if (tooltip_text == tooltip_text_wanted_ && run_loop_->running()) |
| run_loop_->Quit(); |
| } |
| |
| void WaitUntil(const base::string16& tooltip_text) { |
| tooltip_text_wanted_ = tooltip_text; |
| if (base::ContainsValue(tooltips_received_, tooltip_text)) |
| return; |
| run_loop_->Run(); |
| } |
| |
| private: |
| std::unique_ptr<base::RunLoop> run_loop_; |
| base::string16 tooltip_text_wanted_; |
| std::vector<base::string16> tooltips_received_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TooltipMonitor); |
| }; // class TooltipMonitor |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| CrossProcessTooltipTest) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b)")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree() |
| ->root(); |
| |
| 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)); |
| |
| FrameTreeNode* b_node = root->child_at(0); |
| |
| RenderWidgetHostViewBase* rwhv_a = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| RenderWidgetHostViewBase* rwhv_b = static_cast<RenderWidgetHostViewBase*>( |
| b_node->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| DCHECK(rwhv_a->GetCursorManager()); |
| |
| TooltipMonitor tooltip_monitor(rwhv_a->GetCursorManager()); |
| |
| WaitForHitTestDataOrChildSurfaceReady(b_node->current_frame_host()); |
| |
| // Make sure the point_in_a_frame value is outside the default 8px margin |
| // for the body element. |
| gfx::Point point_in_a_frame(10, 10); |
| gfx::Point point_in_b_frame = |
| rwhv_b->TransformPointToRootCoordSpace(gfx::Point(25, 25)); |
| |
| // Create listeners for mouse events. These are used to verify that the |
| // RenderWidgetHostInputEventRouter is generating MouseLeave, etc for |
| // the right renderers. |
| RenderWidgetHostMouseEventMonitor a_frame_monitor( |
| root->current_frame_host()->GetRenderWidgetHost()); |
| RenderWidgetHostMouseEventMonitor b_frame_monitor( |
| b_node->current_frame_host()->GetRenderWidgetHost()); |
| |
| // Add tooltip text to both the body and the iframe in A. |
| std::string script = |
| "body = document.body.setAttribute('title', 'body_tooltip');\n" |
| "iframe = document.getElementsByTagName('iframe')[0];\n" |
| "iframe.setAttribute('title','iframe_for_b');"; |
| EXPECT_TRUE(ExecuteScript(root->current_frame_host(), script)); |
| |
| // Send mouse events to both A and B. |
| blink::WebMouseEvent mouse_event( |
| blink::WebInputEvent::kMouseMove, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| auto* router = web_contents()->GetInputEventRouter(); |
| |
| // Alternate mouse moves between main frame and the cross-process iframe to |
| // test that the tool tip in the iframe can override the one set by the main |
| // frame renderer, even on a second entry into the iframe. |
| gfx::Point current_point; |
| for (int iteration = 0; iteration < 2; ++iteration) { |
| // The following is a bit of a hack to prevent hitting the same |
| // position/node check in ChromeClient::SetToolTip(). |
| current_point = point_in_a_frame; |
| current_point.Offset(iteration, iteration); |
| SetWebEventPositions(&mouse_event, current_point, rwhv_a); |
| RouteMouseEventAndWaitUntilDispatch(router, rwhv_a, rwhv_a, &mouse_event); |
| EXPECT_TRUE(a_frame_monitor.EventWasReceived()); |
| a_frame_monitor.ResetEventReceived(); |
| // B will receive a mouseLeave on all but the first iteration. |
| EXPECT_EQ(iteration != 0, b_frame_monitor.EventWasReceived()); |
| b_frame_monitor.ResetEventReceived(); |
| |
| tooltip_monitor.WaitUntil(base::UTF8ToUTF16("body_tooltip")); |
| tooltip_monitor.Reset(); |
| |
| // Next send a MouseMove to B frame, and A should receive a MouseMove event. |
| current_point = point_in_b_frame; |
| current_point.Offset(iteration, iteration); |
| SetWebEventPositions(&mouse_event, current_point, rwhv_a); |
| RouteMouseEventAndWaitUntilDispatch(router, rwhv_a, rwhv_b, &mouse_event); |
| EXPECT_TRUE(a_frame_monitor.EventWasReceived()); |
| EXPECT_EQ(a_frame_monitor.event().GetType(), |
| blink::WebInputEvent::kMouseMove); |
| a_frame_monitor.ResetEventReceived(); |
| EXPECT_TRUE(b_frame_monitor.EventWasReceived()); |
| b_frame_monitor.ResetEventReceived(); |
| tooltip_monitor.WaitUntil(base::string16()); |
| tooltip_monitor.Reset(); |
| } |
| |
| rwhv_a->GetCursorManager()->SetTooltipObserverForTesting(nullptr); |
| } |
| #endif // !defined(OS_ANDROID) |
| |
| #if defined(OS_ANDROID) |
| // The following test ensures that we don't get a crash if a tooltip is |
| // triggered on Android. This test is nearly identical to |
| // SitePerProcessHitTestBrowserTest.CrossProcessTooltipTestAndroid, except |
| // it omits the tooltip monitor, and all dereferences of GetCursorManager(). |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| CrossProcessTooltipTestAndroid) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b)")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree() |
| ->root(); |
| |
| 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)); |
| |
| FrameTreeNode* b_node = root->child_at(0); |
| |
| RenderWidgetHostViewBase* rwhv_a = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| RenderWidgetHostViewBase* rwhv_b = static_cast<RenderWidgetHostViewBase*>( |
| b_node->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| // On Android we don't expect GetCursorManager() to return anything other |
| // than nullptr. If it did, this test would be unnecessary. |
| DCHECK(!rwhv_a->GetCursorManager()); |
| |
| WaitForHitTestDataOrChildSurfaceReady(b_node->current_frame_host()); |
| |
| // Make sure the point_in_a_frame value is outside the default 8px margin |
| // for the body element. |
| gfx::Point point_in_a_frame(10, 10); |
| gfx::Point point_in_b_frame = |
| rwhv_b->TransformPointToRootCoordSpace(gfx::Point(25, 25)); |
| |
| // Create listeners for mouse events. These are used to verify that the |
| // RenderWidgetHostInputEventRouter is generating MouseLeave, etc for |
| // the right renderers. |
| RenderWidgetHostMouseEventMonitor a_frame_monitor( |
| root->current_frame_host()->GetRenderWidgetHost()); |
| RenderWidgetHostMouseEventMonitor b_frame_monitor( |
| b_node->current_frame_host()->GetRenderWidgetHost()); |
| |
| // Add tooltip text to both the body and the iframe in A. |
| std::string script_a = |
| "body = document.body.setAttribute('title', 'body_a_tooltip');\n" |
| "iframe = document.getElementsByTagName('iframe')[0];\n" |
| "iframe.setAttribute('title','iframe_for_b');"; |
| EXPECT_TRUE(ExecuteScript(root->current_frame_host(), script_a)); |
| std::string script_b = |
| "body = document.body.setAttribute('title', 'body_b_tooltip');"; |
| EXPECT_TRUE(ExecuteScript(b_node->current_frame_host(), script_b)); |
| |
| // Send mouse events to both A and B. |
| blink::WebMouseEvent mouse_event( |
| blink::WebInputEvent::kMouseMove, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| auto* router = web_contents()->GetInputEventRouter(); |
| |
| // Alternate mouse moves between main frame and the cross-process iframe to |
| // test that the tool tip in the iframe can override the one set by the main |
| // frame renderer, even on a second entry into the iframe. |
| gfx::Point current_point; |
| for (int iteration = 0; iteration < 2; ++iteration) { |
| // The following is a bit of a hack to prevent hitting the same |
| // position/node check in ChromeClient::SetToolTip(). |
| current_point = point_in_a_frame; |
| current_point.Offset(iteration, iteration); |
| SetWebEventPositions(&mouse_event, current_point, rwhv_a); |
| RouteMouseEventAndWaitUntilDispatch(router, rwhv_a, rwhv_a, &mouse_event); |
| EXPECT_TRUE(a_frame_monitor.EventWasReceived()); |
| a_frame_monitor.ResetEventReceived(); |
| // B will receive a mouseLeave on all but the first iteration. |
| EXPECT_EQ(iteration != 0, b_frame_monitor.EventWasReceived()); |
| b_frame_monitor.ResetEventReceived(); |
| |
| // Next send a MouseMove to B frame, and A should receive a MouseMove event. |
| current_point = point_in_b_frame; |
| current_point.Offset(iteration, iteration); |
| SetWebEventPositions(&mouse_event, current_point, rwhv_a); |
| RouteMouseEventAndWaitUntilDispatch(router, rwhv_a, rwhv_b, &mouse_event); |
| EXPECT_TRUE(a_frame_monitor.EventWasReceived()); |
| EXPECT_EQ(a_frame_monitor.event().GetType(), |
| blink::WebInputEvent::kMouseMove); |
| a_frame_monitor.ResetEventReceived(); |
| EXPECT_TRUE(b_frame_monitor.EventWasReceived()); |
| b_frame_monitor.ResetEventReceived(); |
| } |
| |
| // This is an (arbitrary) delay to allow the test to crash if it's going to. |
| base::RunLoop run_loop; |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, run_loop.QuitClosure(), TestTimeouts::action_max_timeout()); |
| run_loop.Run(); |
| } |
| #endif // defined(OS_ANDROID) |
| |
| // This test verifies that MouseEnter and MouseLeave events fire correctly |
| // when the mouse cursor moves between processes. |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| CrossProcessMouseEnterAndLeaveTest) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b,c(d))")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| RenderFrameSubmissionObserver render_frame_submission_observer( |
| shell()->web_contents()); |
| |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree() |
| ->root(); |
| |
| EXPECT_EQ( |
| " Site A ------------ proxies for B C D\n" |
| " |--Site B ------- proxies for A C D\n" |
| " +--Site C ------- proxies for A B D\n" |
| " +--Site D -- proxies for A B C\n" |
| "Where A = http://a.com/\n" |
| " B = http://b.com/\n" |
| " C = http://c.com/\n" |
| " D = http://d.com/", |
| DepictFrameTree(root)); |
| |
| FrameTreeNode* b_node = root->child_at(0); |
| FrameTreeNode* c_node = root->child_at(1); |
| FrameTreeNode* d_node = c_node->child_at(0); |
| |
| RenderWidgetHostViewBase* rwhv_a = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| RenderWidgetHostViewBase* rwhv_b = static_cast<RenderWidgetHostViewBase*>( |
| b_node->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| RenderWidgetHostViewBase* rwhv_d = static_cast<RenderWidgetHostViewBase*>( |
| d_node->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| // Verifying surfaces are ready in B and D are sufficient, since other |
| // surfaces contain at least one of them. |
| WaitForHitTestDataOrChildSurfaceReady(b_node->current_frame_host()); |
| WaitForHitTestDataOrChildSurfaceReady(d_node->current_frame_host()); |
| |
| // Create listeners for mouse events. These are used to verify that the |
| // RenderWidgetHostInputEventRouter is generating MouseLeave, etc for |
| // the right renderers. |
| RenderWidgetHostMouseEventMonitor root_frame_monitor( |
| root->current_frame_host()->GetRenderWidgetHost()); |
| RenderWidgetHostMouseEventMonitor a_frame_monitor( |
| root->current_frame_host()->GetRenderWidgetHost()); |
| RenderWidgetHostMouseEventMonitor b_frame_monitor( |
| b_node->current_frame_host()->GetRenderWidgetHost()); |
| RenderWidgetHostMouseEventMonitor c_frame_monitor( |
| c_node->current_frame_host()->GetRenderWidgetHost()); |
| RenderWidgetHostMouseEventMonitor d_frame_monitor( |
| d_node->current_frame_host()->GetRenderWidgetHost()); |
| |
| float scale_factor = |
| render_frame_submission_observer.LastRenderFrameMetadata() |
| .page_scale_factor; |
| |
| // Get the view bounds of the child iframe, which should account for the |
| // relative offset of its direct parent within the root frame, for use in |
| // targeting the input event. |
| gfx::Rect a_bounds = rwhv_a->GetViewBounds(); |
| gfx::Rect b_bounds = rwhv_b->GetViewBounds(); |
| gfx::Rect d_bounds = rwhv_d->GetViewBounds(); |
| |
| gfx::Point point_in_a_frame(2, 2); |
| gfx::Point point_in_b_frame( |
| gfx::ToCeiledInt((b_bounds.x() - a_bounds.x() + 25) * scale_factor), |
| gfx::ToCeiledInt((b_bounds.y() - a_bounds.y() + 25) * scale_factor)); |
| gfx::Point point_in_d_frame( |
| gfx::ToCeiledInt((d_bounds.x() - a_bounds.x() + 25) * scale_factor), |
| gfx::ToCeiledInt((d_bounds.y() - a_bounds.y() + 25) * scale_factor)); |
| |
| blink::WebMouseEvent mouse_event( |
| blink::WebInputEvent::kMouseMove, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| SetWebEventPositions(&mouse_event, point_in_a_frame, rwhv_a); |
| |
| auto* router = web_contents()->GetInputEventRouter(); |
| |
| // Send an initial MouseMove to the root view, which shouldn't affect the |
| // other renderers. |
| RouteMouseEventAndWaitUntilDispatch(router, rwhv_a, rwhv_a, &mouse_event); |
| EXPECT_TRUE(a_frame_monitor.EventWasReceived()); |
| a_frame_monitor.ResetEventReceived(); |
| EXPECT_FALSE(b_frame_monitor.EventWasReceived()); |
| EXPECT_FALSE(c_frame_monitor.EventWasReceived()); |
| EXPECT_FALSE(d_frame_monitor.EventWasReceived()); |
| |
| // Next send a MouseMove to B frame, which shouldn't affect C or D but |
| // A should receive a MouseMove event. |
| SetWebEventPositions(&mouse_event, point_in_b_frame, rwhv_a); |
| RouteMouseEventAndWaitUntilDispatch(router, rwhv_a, rwhv_b, &mouse_event); |
| EXPECT_TRUE(a_frame_monitor.EventWasReceived()); |
| EXPECT_EQ(a_frame_monitor.event().GetType(), |
| blink::WebInputEvent::kMouseMove); |
| a_frame_monitor.ResetEventReceived(); |
| EXPECT_TRUE(b_frame_monitor.EventWasReceived()); |
| b_frame_monitor.ResetEventReceived(); |
| EXPECT_FALSE(c_frame_monitor.EventWasReceived()); |
| EXPECT_FALSE(d_frame_monitor.EventWasReceived()); |
| |
| // Next send a MouseMove to D frame, which should have side effects in every |
| // other RenderWidgetHostView. |
| SetWebEventPositions(&mouse_event, point_in_d_frame, rwhv_a); |
| RouteMouseEventAndWaitUntilDispatch(router, rwhv_a, rwhv_d, &mouse_event); |
| EXPECT_TRUE(a_frame_monitor.EventWasReceived()); |
| EXPECT_EQ(a_frame_monitor.event().GetType(), |
| blink::WebInputEvent::kMouseMove); |
| EXPECT_TRUE(b_frame_monitor.EventWasReceived()); |
| EXPECT_EQ(b_frame_monitor.event().GetType(), |
| blink::WebInputEvent::kMouseLeave); |
| EXPECT_TRUE(c_frame_monitor.EventWasReceived()); |
| EXPECT_EQ(c_frame_monitor.event().GetType(), |
| blink::WebInputEvent::kMouseMove); |
| EXPECT_TRUE(d_frame_monitor.EventWasReceived()); |
| } |
| |
| // Verify that mouse capture works on a RenderWidgetHostView level. |
| // This test checks that a MouseDown triggers mouse capture when it hits |
| // a scrollbar thumb or a subframe, and does not trigger mouse |
| // capture if it hits an element in the main frame. |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| CrossProcessMouseCapture) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "/frame_tree/page_with_large_scrollable_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* child_node = root->child_at(0); |
| ASSERT_EQ( |
| " Site A ------------ proxies for B\n" |
| " +--Site B ------- proxies for A\n" |
| "Where A = http://127.0.0.1/\n" |
| " B = http://baz.com/", |
| DepictFrameTree(root)); |
| |
| // Create listeners for mouse events. |
| RenderWidgetHostMouseEventMonitor main_frame_monitor( |
| root->current_frame_host()->GetRenderWidgetHost()); |
| RenderWidgetHostMouseEventMonitor child_frame_monitor( |
| child_node->current_frame_host()->GetRenderWidgetHost()); |
| |
| RenderWidgetHostInputEventRouter* router = |
| web_contents()->GetInputEventRouter(); |
| |
| RenderWidgetHostViewBase* root_view = 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()); |
| |
| float scale_factor = |
| render_frame_submission_observer.LastRenderFrameMetadata() |
| .page_scale_factor; |
| |
| // Get the view bounds of the child iframe, which should account for the |
| // relative offset of its direct parent within the root frame, for use in |
| // targeting the input event. |
| gfx::Rect bounds = rwhv_child->GetViewBounds(); |
| int child_frame_target_x = gfx::ToCeiledInt( |
| (bounds.x() - root_view->GetViewBounds().x() + 5) * scale_factor); |
| int child_frame_target_y = gfx::ToCeiledInt( |
| (bounds.y() - root_view->GetViewBounds().y() + 5) * scale_factor); |
| |
| scoped_refptr<SetMouseCaptureInterceptor> child_interceptor = |
| new SetMouseCaptureInterceptor(static_cast<RenderWidgetHostImpl*>( |
| child_node->current_frame_host()->GetRenderWidgetHost())); |
| |
| // Target MouseDown to child frame. |
| blink::WebMouseEvent mouse_event( |
| blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| mouse_event.button = blink::WebPointerProperties::Button::kLeft; |
| SetWebEventPositions(&mouse_event, |
| gfx::Point(child_frame_target_x, child_frame_target_y), |
| root_view); |
| mouse_event.click_count = 1; |
| main_frame_monitor.ResetEventReceived(); |
| child_frame_monitor.ResetEventReceived(); |
| RouteMouseEventAndWaitUntilDispatch(router, root_view, rwhv_child, |
| &mouse_event); |
| |
| EXPECT_FALSE(main_frame_monitor.EventWasReceived()); |
| EXPECT_TRUE(child_frame_monitor.EventWasReceived()); |
| |
| // Wait for the mouse capture message. |
| child_interceptor->Wait(); |
| EXPECT_TRUE(child_interceptor->Capturing()); |
| // Yield the thread, in order to let the capture message be processed by its |
| // actual handler. |
| base::RunLoop().RunUntilIdle(); |
| |
| // Target MouseMove at main frame. The child frame is now capturing input, |
| // so it should receive the event instead. |
| mouse_event.SetType(blink::WebInputEvent::kMouseMove); |
| mouse_event.SetModifiers(blink::WebInputEvent::kLeftButtonDown); |
| SetWebEventPositions(&mouse_event, gfx::Point(1, 1), root_view); |
| RouteMouseEventAndWaitUntilDispatch(router, root_view, rwhv_child, |
| &mouse_event); |
| |
| // Dispatch twice because the router generates an extra MouseLeave for the |
| // main frame. |
| main_frame_monitor.ResetEventReceived(); |
| child_frame_monitor.ResetEventReceived(); |
| RouteMouseEventAndWaitUntilDispatch(router, root_view, rwhv_child, |
| &mouse_event); |
| EXPECT_FALSE(main_frame_monitor.EventWasReceived()); |
| EXPECT_TRUE(child_frame_monitor.EventWasReceived()); |
| |
| // MouseUp releases capture. |
| mouse_event.SetType(blink::WebInputEvent::kMouseUp); |
| mouse_event.SetModifiers(blink::WebInputEvent::kNoModifiers); |
| SetWebEventPositions(&mouse_event, gfx::Point(1, 1), root_view); |
| RouteMouseEventAndWaitUntilDispatch(router, root_view, rwhv_child, |
| &mouse_event); |
| |
| child_interceptor->Wait(); |
| EXPECT_FALSE(child_interceptor->Capturing()); |
| |
| // Targeting a MouseDown to the main frame should not initiate capture. |
| mouse_event.SetType(blink::WebInputEvent::kMouseDown); |
| mouse_event.SetModifiers(blink::WebInputEvent::kLeftButtonDown); |
| mouse_event.button = blink::WebPointerProperties::Button::kLeft; |
| SetWebEventPositions(&mouse_event, gfx::Point(1, 1), root_view); |
| mouse_event.click_count = 1; |
| main_frame_monitor.ResetEventReceived(); |
| child_frame_monitor.ResetEventReceived(); |
| RouteMouseEventAndWaitUntilDispatch(router, root_view, root_view, |
| &mouse_event); |
| |
| EXPECT_TRUE(main_frame_monitor.EventWasReceived()); |
| EXPECT_FALSE(child_frame_monitor.EventWasReceived()); |
| |
| // Target MouseMove at child frame. Without capture, this should be |
| // dispatched to the child frame. |
| mouse_event.SetType(blink::WebInputEvent::kMouseMove); |
| SetWebEventPositions(&mouse_event, |
| gfx::Point(child_frame_target_x, child_frame_target_y), |
| root_view); |
| RouteMouseEventAndWaitUntilDispatch(router, root_view, rwhv_child, |
| &mouse_event); |
| |
| main_frame_monitor.ResetEventReceived(); |
| child_frame_monitor.ResetEventReceived(); |
| // Again, twice because of the transition MouseMove sent to the main |
| // frame. |
| RouteMouseEventAndWaitUntilDispatch(router, root_view, rwhv_child, |
| &mouse_event); |
| EXPECT_FALSE(main_frame_monitor.EventWasReceived()); |
| EXPECT_TRUE(child_frame_monitor.EventWasReceived()); |
| EXPECT_FALSE(child_interceptor->Capturing()); |
| |
| // No release capture events since the capture statu doesn't change. |
| mouse_event.SetType(blink::WebInputEvent::kMouseUp); |
| mouse_event.SetModifiers(blink::WebInputEvent::kNoModifiers); |
| SetWebEventPositions(&mouse_event, |
| gfx::Point(child_frame_target_x, child_frame_target_y), |
| root_view); |
| RouteMouseEventAndWaitUntilDispatch(router, root_view, rwhv_child, |
| &mouse_event); |
| |
| EXPECT_FALSE(child_interceptor->Capturing()); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Targeting a scrollbar with a click doesn't work on Mac or Android. |
| #if !defined(OS_MACOSX) && !defined(OS_ANDROID) |
| scoped_refptr<SetMouseCaptureInterceptor> root_interceptor = |
| new SetMouseCaptureInterceptor(static_cast<RenderWidgetHostImpl*>( |
| root->current_frame_host()->GetRenderWidgetHost())); |
| |
| // Now send a MouseDown to target the thumb part of the scroll bar, which |
| // should initiate mouse capture for the main frame. |
| mouse_event.SetType(blink::WebInputEvent::kMouseDown); |
| mouse_event.SetModifiers(blink::WebInputEvent::kLeftButtonDown); |
| SetWebEventPositions(&mouse_event, gfx::Point(100, 25), root_view); |
| RouteMouseEventAndWaitUntilDispatch(router, root_view, root_view, |
| &mouse_event); |
| EXPECT_TRUE(main_frame_monitor.EventWasReceived()); |
| |
| // Wait for the mouse capture message. |
| root_interceptor->Wait(); |
| EXPECT_TRUE(root_interceptor->Capturing()); |
| base::RunLoop().RunUntilIdle(); |
| |
| main_frame_monitor.ResetEventReceived(); |
| child_frame_monitor.ResetEventReceived(); |
| |
| // Now that the main frame is capturing, a MouseMove targeted to the child |
| // frame should be received by the main frame. |
| mouse_event.SetType(blink::WebInputEvent::kMouseMove); |
| SetWebEventPositions(&mouse_event, |
| gfx::Point(child_frame_target_x, child_frame_target_y), |
| root_view); |
| RouteMouseEventAndWaitUntilDispatch(router, root_view, root_view, |
| &mouse_event); |
| main_frame_monitor.ResetEventReceived(); |
| child_frame_monitor.ResetEventReceived(); |
| RouteMouseEventAndWaitUntilDispatch(router, root_view, root_view, |
| &mouse_event); |
| EXPECT_TRUE(main_frame_monitor.EventWasReceived()); |
| EXPECT_FALSE(child_frame_monitor.EventWasReceived()); |
| |
| // A MouseUp sent anywhere should cancel the mouse capture. |
| mouse_event.SetType(blink::WebInputEvent::kMouseUp); |
| mouse_event.SetModifiers(blink::WebInputEvent::kNoModifiers); |
| SetWebEventPositions(&mouse_event, |
| gfx::Point(child_frame_target_x, child_frame_target_y), |
| root_view); |
| RouteMouseEventAndWaitUntilDispatch(router, root_view, root_view, |
| &mouse_event); |
| |
| root_interceptor->Wait(); |
| EXPECT_FALSE(root_interceptor->Capturing()); |
| #endif // !defined(OS_MACOSX) && !defined(OS_ANDROID) |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| MouseCaptureOnDragSelection) { |
| 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* child_node = root->child_at(0); |
| ASSERT_EQ( |
| " Site A ------------ proxies for B\n" |
| " +--Site B ------- proxies for A\n" |
| "Where A = http://127.0.0.1/\n" |
| " B = http://baz.com/", |
| DepictFrameTree(root)); |
| |
| // Create listeners for mouse events. |
| RenderWidgetHostMouseEventMonitor main_frame_monitor( |
| root->current_frame_host()->GetRenderWidgetHost()); |
| RenderWidgetHostMouseEventMonitor child_frame_monitor( |
| child_node->current_frame_host()->GetRenderWidgetHost()); |
| |
| RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>( |
| child_node->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host()); |
| |
| scoped_refptr<SetMouseCaptureInterceptor> interceptor = |
| new SetMouseCaptureInterceptor(static_cast<RenderWidgetHostImpl*>( |
| child_node->current_frame_host()->GetRenderWidgetHost())); |
| |
| // Target MouseDown to child frame. |
| blink::WebMouseEvent mouse_event( |
| blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| mouse_event.button = blink::WebPointerProperties::Button::kLeft; |
| mouse_event.click_count = 1; |
| main_frame_monitor.ResetEventReceived(); |
| child_frame_monitor.ResetEventReceived(); |
| DispatchMouseEventAndWaitUntilDispatch(web_contents(), mouse_event, |
| rwhv_child, gfx::PointF(15.0, 5.0), |
| rwhv_child, gfx::PointF(15.0, 5.0)); |
| |
| EXPECT_FALSE(main_frame_monitor.EventWasReceived()); |
| EXPECT_TRUE(child_frame_monitor.EventWasReceived()); |
| // Wait for the mouse capture message. |
| interceptor->Wait(); |
| EXPECT_TRUE(interceptor->Capturing()); |
| |
| main_frame_monitor.ResetEventReceived(); |
| child_frame_monitor.ResetEventReceived(); |
| |
| // Target MouseMove to child frame to start drag. This should cause the |
| // child to start capturing mouse input. |
| mouse_event.SetType(blink::WebInputEvent::kMouseMove); |
| mouse_event.SetModifiers(blink::WebInputEvent::kLeftButtonDown); |
| DispatchMouseEventAndWaitUntilDispatch(web_contents(), mouse_event, |
| rwhv_child, gfx::PointF(5.0, 5.0), |
| rwhv_child, gfx::PointF(5.0, 5.0)); |
| |
| // Dispatch twice because the router generates an extra MouseLeave for the |
| // main frame. |
| main_frame_monitor.ResetEventReceived(); |
| child_frame_monitor.ResetEventReceived(); |
| DispatchMouseEventAndWaitUntilDispatch(web_contents(), mouse_event, |
| rwhv_child, gfx::PointF(5.0, 5.0), |
| rwhv_child, gfx::PointF(5.0, 5.0)); |
| |
| EXPECT_FALSE(main_frame_monitor.EventWasReceived()); |
| EXPECT_TRUE(child_frame_monitor.EventWasReceived()); |
| main_frame_monitor.ResetEventReceived(); |
| child_frame_monitor.ResetEventReceived(); |
| |
| EXPECT_TRUE(interceptor->Capturing()); |
| |
| // Yield the thread, in order to let the capture message be processed by its |
| // actual handler. |
| { |
| base::RunLoop loop; |
| base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, |
| loop.QuitClosure()); |
| loop.Run(); |
| } |
| |
| // Now that the child frame is capturing, a MouseMove targeted to the main |
| // frame should be received by the child frame. |
| DispatchMouseEventAndWaitUntilDispatch(web_contents(), mouse_event, |
| rwhv_child, gfx::PointF(-25.0, -25.0), |
| rwhv_child, gfx::PointF(-25.0, -25.0)); |
| EXPECT_FALSE(main_frame_monitor.EventWasReceived()); |
| EXPECT_TRUE(child_frame_monitor.EventWasReceived()); |
| main_frame_monitor.ResetEventReceived(); |
| child_frame_monitor.ResetEventReceived(); |
| |
| // A MouseUp sent anywhere should cancel the mouse capture. |
| mouse_event.SetType(blink::WebInputEvent::kMouseUp); |
| mouse_event.SetModifiers(0); |
| DispatchMouseEventAndWaitUntilDispatch(web_contents(), mouse_event, |
| rwhv_child, gfx::PointF(-25.0, -25.0), |
| rwhv_child, gfx::PointF(-25.0, -25.0)); |
| |
| interceptor->Wait(); |
| EXPECT_FALSE(interceptor->Capturing()); |
| } |
| |
| // Verify that when a divider within a frameset is clicked, mouse capture is |
| // initiated. |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| MouseCaptureOnFramesetResize) { |
| GURL main_url(embedded_test_server()->GetURL("/page_with_frameset.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| FrameTreeNode* root = web_contents()->GetFrameTree()->root(); |
| RenderWidgetHost* widget_host = |
| root->current_frame_host()->GetRenderWidgetHost(); |
| RenderWidgetHostViewBase* rwhv_root = |
| static_cast<RenderWidgetHostViewBase*>(widget_host->GetView()); |
| |
| scoped_refptr<SetMouseCaptureInterceptor> interceptor = |
| new SetMouseCaptureInterceptor( |
| static_cast<RenderWidgetHostImpl*>(widget_host)); |
| |
| gfx::PointF click_point = |
| gfx::PointF(rwhv_root->GetViewBounds().width() / 2, 20); |
| |
| // Click on the divider bar that initiates resize. |
| DispatchMouseEventAndWaitUntilDispatch(web_contents(), rwhv_root, click_point, |
| rwhv_root, click_point); |
| |
| // Wait for the mouse capture message. |
| interceptor->Wait(); |
| EXPECT_TRUE(interceptor->Capturing()); |
| } |
| |
| // There are no cursors on Android. |
| #if !defined(OS_ANDROID) |
| class CursorMessageFilter : public content::BrowserMessageFilter { |
| public: |
| CursorMessageFilter() |
| : content::BrowserMessageFilter(WidgetMsgStart), |
| message_loop_runner_(new content::MessageLoopRunner), |
| last_set_cursor_routing_id_(MSG_ROUTING_NONE) {} |
| |
| bool OnMessageReceived(const IPC::Message& message) override { |
| if (message.type() == WidgetHostMsg_SetCursor::ID) { |
| base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::UI}, |
| base::BindOnce(&CursorMessageFilter::OnSetCursor, |
| this, message.routing_id())); |
| } |
| return false; |
| } |
| |
| void OnSetCursor(int routing_id) { |
| last_set_cursor_routing_id_ = routing_id; |
| message_loop_runner_->Quit(); |
| } |
| |
| int last_set_cursor_routing_id() const { return last_set_cursor_routing_id_; } |
| |
| void Wait() { |
| // Do not reset the cursor, as the cursor may already have been set (and |
| // Quit() already called on |message_loop_runner_|). |
| message_loop_runner_->Run(); |
| } |
| |
| private: |
| ~CursorMessageFilter() override {} |
| |
| scoped_refptr<content::MessageLoopRunner> message_loop_runner_; |
| int last_set_cursor_routing_id_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CursorMessageFilter); |
| }; |
| |
| namespace { |
| |
| // Verify that we receive a mouse cursor update message when we mouse over |
| // a text field contained in an out-of-process iframe. |
| void CursorUpdateReceivedFromCrossSiteIframeHelper( |
| 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()); |
| FrameTreeNode* root = web_contents->GetFrameTree()->root(); |
| |
| FrameTreeNode* child_node = root->child_at(0); |
| EXPECT_NE(shell->web_contents()->GetSiteInstance(), |
| child_node->current_frame_host()->GetSiteInstance()); |
| |
| WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host()); |
| |
| scoped_refptr<CursorMessageFilter> filter = new CursorMessageFilter(); |
| child_node->current_frame_host()->GetProcess()->AddFilter(filter.get()); |
| |
| RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| RenderWidgetHost* rwh_child = |
| root->child_at(0)->current_frame_host()->GetRenderWidgetHost(); |
| RenderWidgetHostViewBase* child_view = |
| static_cast<RenderWidgetHostViewBase*>(rwh_child->GetView()); |
| |
| // This should only return nullptr on Android. |
| EXPECT_TRUE(root_view->GetCursorManager()); |
| |
| WebCursor cursor; |
| EXPECT_FALSE( |
| root_view->GetCursorManager()->GetCursorForTesting(root_view, cursor)); |
| EXPECT_FALSE( |
| root_view->GetCursorManager()->GetCursorForTesting(child_view, cursor)); |
| |
| // Send a MouseMove to the subframe. The frame contains text, and moving the |
| // mouse over it should cause the renderer to send a mouse cursor update. |
| blink::WebMouseEvent mouse_event( |
| blink::WebInputEvent::kMouseMove, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| SetWebEventPositions(&mouse_event, gfx::Point(60, 60), root_view); |
| auto* router = web_contents->GetInputEventRouter(); |
| RenderWidgetHostMouseEventMonitor child_monitor( |
| child_view->GetRenderWidgetHost()); |
| RenderWidgetHostMouseEventMonitor root_monitor( |
| root_view->GetRenderWidgetHost()); |
| RouteMouseEventAndWaitUntilDispatch(router, root_view, child_view, |
| &mouse_event); |
| // The child_view should receive a mouse-move event. |
| EXPECT_TRUE(child_monitor.EventWasReceived()); |
| EXPECT_EQ(blink::WebInputEvent::kMouseMove, child_monitor.event().GetType()); |
| EXPECT_NEAR(8, child_monitor.event().PositionInWidget().x, kHitTestTolerance); |
| EXPECT_NEAR(8, child_monitor.event().PositionInWidget().y, kHitTestTolerance); |
| |
| // The root_view should also receive a mouse-move event. |
| EXPECT_TRUE(root_monitor.EventWasReceived()); |
| EXPECT_EQ(blink::WebInputEvent::kMouseMove, root_monitor.event().GetType()); |
| EXPECT_EQ(60, root_monitor.event().PositionInWidget().x); |
| EXPECT_EQ(60, root_monitor.event().PositionInWidget().y); |
| |
| // CursorMessageFilter::Wait() implicitly tests whether we receive a |
| // WidgetHostMsg_SetCursor message from the renderer process, because it does |
| // does not return otherwise. |
| filter->Wait(); |
| EXPECT_EQ(filter->last_set_cursor_routing_id(), rwh_child->GetRoutingID()); |
| |
| // Yield to ensure that the SetCursor message is processed by its real |
| // handler. |
| { |
| base::RunLoop loop; |
| base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, |
| loop.QuitClosure()); |
| loop.Run(); |
| } |
| |
| // The root_view receives a mouse-move event on top of the iframe, which does |
| // not send a cursor update. |
| EXPECT_FALSE( |
| root_view->GetCursorManager()->GetCursorForTesting(root_view, cursor)); |
| EXPECT_TRUE( |
| root_view->GetCursorManager()->GetCursorForTesting(child_view, cursor)); |
| // Since this moused over a text box, this should not be the default cursor. |
| CursorInfo cursor_info; |
| cursor.GetCursorInfo(&cursor_info); |
| EXPECT_EQ(cursor_info.type, blink::WebCursorInfo::kTypeIBeam); |
| } |
| |
| } // namespace |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| CursorUpdateReceivedFromCrossSiteIframe) { |
| CursorUpdateReceivedFromCrossSiteIframeHelper(shell(), |
| embedded_test_server()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest, |
| CursorUpdateReceivedFromCrossSiteIframe) { |
| CursorUpdateReceivedFromCrossSiteIframeHelper(shell(), |
| embedded_test_server()); |
| } |
| #endif // !defined(OS_ANDROID) |
| |
| #if defined(USE_AURA) |
| // Browser process hit testing is not implemented on Android, and these tests |
| // require Aura for RenderWidgetHostViewAura::OnTouchEvent(). |
| // https://crbug.com/491334 |
| |
| // Ensure that scroll events can be cancelled with a wheel handler. |
| // https://crbug.com/698195 |
| |
| class SitePerProcessMouseWheelHitTestBrowserTest |
| : public SitePerProcessHitTestBrowserTest { |
| public: |
| SitePerProcessMouseWheelHitTestBrowserTest() : rwhv_root_(nullptr) {} |
| |
| void SetupWheelAndScrollHandlers(content::RenderFrameHostImpl* rfh) { |
| // Set up event handlers. The wheel event handler calls prevent default on |
| // alternate events, so only every other wheel generates a scroll. The fact |
| // that any scroll events fire is dependent on the event going to the main |
| // thread, which requires the nonFastScrollableRegion be set correctly |
| // on the compositor. |
| std::string script = |
| "wheel_count = 0;" |
| "function wheel_handler(e) {" |
| " wheel_count++;" |
| " if (wheel_count % 2 == 0)" |
| " e.preventDefault();\n" |
| " domAutomationController.send('wheel: ' + wheel_count);" |
| "}" |
| "function scroll_handler(e) {" |
| " domAutomationController.send('scroll: ' + wheel_count);" |
| "}" |
| "scroll_div = document.getElementById('scrollable_div');" |
| "scroll_div.addEventListener('wheel', wheel_handler);" |
| "scroll_div.addEventListener('scroll', scroll_handler);" |
| "document.body.style.background = 'black';"; |
| |
| content::DOMMessageQueue msg_queue; |
| std::string reply; |
| EXPECT_TRUE(ExecuteScript(rfh, script)); |
| |
| // Wait until renderer's compositor thread is synced. Otherwise the event |
| // handler won't be installed when the event arrives. |
| { |
| MainThreadFrameObserver observer(rfh->GetRenderWidgetHost()); |
| observer.Wait(); |
| } |
| } |
| |
| void SendMouseWheel(gfx::Point location) { |
| DCHECK(rwhv_root_); |
| 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_); |
| rwhv_root_->OnScrollEvent(&scroll_event); |
| } |
| |
| void set_rwhv_root(RenderWidgetHostViewAura* rwhv_root) { |
| rwhv_root_ = rwhv_root; |
| } |
| |
| void RunTest(gfx::Point pos, RenderWidgetHostViewBase* expected_target) { |
| content::DOMMessageQueue msg_queue; |
| std::string reply; |
| |
| auto* rwhv_root = static_cast<RenderWidgetHostViewAura*>( |
| web_contents()->GetRenderWidgetHostView()); |
| set_rwhv_root(rwhv_root); |
| |
| // Set the wheel scroll latching timeout to a large value to make sure |
| // that the timer doesn't expire for the duration of the test. |
| rwhv_root->event_handler()->set_mouse_wheel_wheel_phase_handler_timeout( |
| TestTimeouts::action_max_timeout()); |
| |
| InputEventAckWaiter waiter(expected_target->GetRenderWidgetHost(), |
| blink::WebInputEvent::kMouseWheel); |
| SendMouseWheel(pos); |
| waiter.Wait(); |
| |
| // Expect both wheel and scroll handlers to fire. |
| EXPECT_TRUE(msg_queue.WaitForMessage(&reply)); |
| EXPECT_EQ("\"wheel: 1\"", reply); |
| EXPECT_TRUE(msg_queue.WaitForMessage(&reply)); |
| EXPECT_EQ("\"scroll: 1\"", reply); |
| |
| SendMouseWheel(pos); |
| |
| // Even though even number events are prevented by default since the first |
| // wheel event is not prevented by default, the rest of the wheel events |
| // will be handled nonblocking and the scroll will happen. |
| EXPECT_TRUE(msg_queue.WaitForMessage(&reply)); |
| EXPECT_EQ("\"wheel: 2\"", reply); |
| EXPECT_TRUE(msg_queue.WaitForMessage(&reply)); |
| EXPECT_EQ("\"scroll: 2\"", reply); |
| |
| SendMouseWheel(pos); |
| |
| // Odd number of wheels, expect both wheel and scroll handlers to fire |
| // again. |
| EXPECT_TRUE(msg_queue.WaitForMessage(&reply)); |
| EXPECT_EQ("\"wheel: 3\"", reply); |
| EXPECT_TRUE(msg_queue.WaitForMessage(&reply)); |
| EXPECT_EQ("\"scroll: 3\"", reply); |
| } |
| |
| private: |
| RenderWidgetHostViewAura* rwhv_root_; |
| }; |
| |
| // Fails on Windows official build, see // https://crbug.com/800822 |
| #if defined(OS_WIN) |
| #define MAYBE_MultipleSubframeWheelEventsOnMainThread \ |
| DISABLED_MultipleSubframeWheelEventsOnMainThread |
| #else |
| #define MAYBE_MultipleSubframeWheelEventsOnMainThread \ |
| MultipleSubframeWheelEventsOnMainThread |
| #endif |
| IN_PROC_BROWSER_TEST_P(SitePerProcessMouseWheelHitTestBrowserTest, |
| MAYBE_MultipleSubframeWheelEventsOnMainThread) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "/frame_tree/page_with_two_positioned_frames.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| FrameTreeNode* root = web_contents()->GetFrameTree()->root(); |
| ASSERT_EQ(2U, root->child_count()); |
| |
| GURL frame_url(embedded_test_server()->GetURL( |
| "b.com", "/page_with_scrollable_div.html")); |
| // To test for https://bugs.chromium.org/p/chromium/issues/detail?id=820232 |
| // it's important that both subframes are in the same renderer process, so |
| // we load the same URL in each case. |
| NavigateFrameToURL(root->child_at(0), frame_url); |
| NavigateFrameToURL(root->child_at(1), frame_url); |
| |
| for (int frame_index = 0; frame_index < 2; frame_index++) { |
| // Synchronize with the child and parent renderers to guarantee that the |
| // surface information required for event hit testing is ready. |
| RenderWidgetHostViewBase* child_rwhv = |
| static_cast<RenderWidgetHostViewBase*>( |
| root->child_at(frame_index)->current_frame_host()->GetView()); |
| |
| WaitForHitTestDataOrChildSurfaceReady( |
| root->child_at(frame_index)->current_frame_host()); |
| |
| content::RenderFrameHostImpl* child = |
| root->child_at(frame_index)->current_frame_host(); |
| SetupWheelAndScrollHandlers(child); |
| |
| gfx::Rect bounds = child_rwhv->GetViewBounds(); |
| gfx::Point pos(bounds.x() + 10, bounds.y() + 10); |
| |
| RunTest(pos, child_rwhv); |
| } |
| } |
| |
| // Verifies that test in SubframeWheelEventsOnMainThread also makes sense for |
| // the same page loaded in the mainframe. |
| // Fails on Windows official build, see // https://crbug.com/800822 |
| #if defined(OS_WIN) |
| #define MAYBE_MainframeWheelEventsOnMainThread \ |
| DISABLED_MainframeWheelEventsOnMainThread |
| #else |
| #define MAYBE_MainframeWheelEventsOnMainThread MainframeWheelEventsOnMainThread |
| #endif |
| IN_PROC_BROWSER_TEST_P(SitePerProcessMouseWheelHitTestBrowserTest, |
| MAYBE_MainframeWheelEventsOnMainThread) { |
| GURL main_url( |
| embedded_test_server()->GetURL("/page_with_scrollable_div.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| FrameTreeNode* root = web_contents()->GetFrameTree()->root(); |
| content::RenderFrameHostImpl* rfhi = root->current_frame_host(); |
| SetupWheelAndScrollHandlers(rfhi); |
| |
| gfx::Point pos(10, 10); |
| |
| RunTest(pos, rfhi->GetRenderWidgetHost()->GetView()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessMouseWheelHitTestBrowserTest, |
| InputEventRouterWheelTargetTest) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "/frame_tree/page_with_positioned_nested_frames.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| auto* rwhv_root = static_cast<RenderWidgetHostViewAura*>( |
| web_contents()->GetRenderWidgetHostView()); |
| set_rwhv_root(rwhv_root); |
| |
| FrameTreeNode* root = web_contents()->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| GURL frame_url(embedded_test_server()->GetURL( |
| "b.com", "/page_with_scrollable_div.html")); |
| NavigateFrameToURL(root->child_at(0), frame_url); |
| |
| // Synchronize with the child and parent renderers to guarantee that the |
| // surface information required for event hit testing is ready. |
| RenderWidgetHostViewBase* child_rwhv = static_cast<RenderWidgetHostViewBase*>( |
| root->child_at(0)->current_frame_host()->GetView()); |
| WaitForHitTestDataOrChildSurfaceReady( |
| root->child_at(0)->current_frame_host()); |
| |
| RenderWidgetHostInputEventRouter* router = |
| web_contents()->GetInputEventRouter(); |
| |
| // Send a mouse wheel event to child. |
| gfx::Rect bounds = child_rwhv->GetViewBounds(); |
| gfx::Point pos(bounds.x() + 10, bounds.y() + 10); |
| InputEventAckWaiter waiter(child_rwhv->GetRenderWidgetHost(), |
| blink::WebInputEvent::kMouseWheel); |
| SendMouseWheel(pos); |
| waiter.Wait(); |
| |
| EXPECT_EQ(child_rwhv, router->wheel_target_); |
| |
| // Send a mouse wheel event to the main frame. It will be still routed to |
| // child till the end of current scrolling sequence. Since wheel scroll |
| // latching is enabled by default, we always do sync targeting so |
| // InputEventAckWaiter is not needed here. |
| TestInputEventObserver child_frame_monitor(child_rwhv->GetRenderWidgetHost()); |
| SendMouseWheel(pos); |
| EXPECT_EQ(child_rwhv, router->wheel_target_); |
| |
| // 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)); |
| |
| // Kill the wheel target view process. This must reset the wheel_target_. |
| RenderProcessHost* child_process = |
| root->child_at(0)->current_frame_host()->GetProcess(); |
| RenderProcessHostWatcher crash_observer( |
| child_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); |
| child_process->Shutdown(0); |
| crash_observer.Wait(); |
| EXPECT_EQ(nullptr, router->wheel_target_); |
| } |
| |
| // Ensure that the positions of mouse wheel events sent to cross-process |
| // subframes account for any change in the position of the subframe during the |
| // scroll sequence. |
| IN_PROC_BROWSER_TEST_P(SitePerProcessMouseWheelHitTestBrowserTest, |
| MouseWheelEventPositionChange) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "/frame_tree/page_with_tall_positioned_frame.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| auto* rwhv_root = static_cast<RenderWidgetHostViewAura*>( |
| web_contents()->GetRenderWidgetHostView()); |
| set_rwhv_root(rwhv_root); |
| |
| FrameTreeNode* root = web_contents()->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| // 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()); |
| |
| RenderWidgetHostInputEventRouter* router = |
| web_contents()->GetInputEventRouter(); |
| |
| 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_FLOAT_EQ(gesture_event.PositionInWidget().x, |
| expected_position.x()); |
| EXPECT_FLOAT_EQ(gesture_event.PositionInWidget().y, |
| expected_position.y()); |
| EXPECT_FLOAT_EQ(root_point.x(), expected_position_in_root.x()); |
| EXPECT_FLOAT_EQ(root_point.y(), expected_position_in_root.y()); |
| return true; |
| }); |
| MainThreadFrameObserver thread_observer(rwhv_root->GetRenderWidgetHost()); |
| |
| // Send a mouse wheel begin event to child. |
| blink::WebMouseWheelEvent scroll_event( |
| blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| gfx::Point child_point_in_root(90, 90); |
| SetWebEventPositions(&scroll_event, child_point_in_root, rwhv_root); |
| scroll_event.delta_x = 0.0f; |
| scroll_event.delta_y = -20.0f; |
| scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan; |
| scroll_event.has_precise_scrolling_deltas = true; |
| |
| { |
| InputEventAckWaiter await_begin_in_child( |
| child_rwhv->GetRenderWidgetHost(), |
| base::BindRepeating(await_gesture_event_with_position, |
| blink::WebInputEvent::kGestureScrollBegin, |
| child_rwhv, gfx::PointF(38, 38), |
| gfx::PointF(child_point_in_root))); |
| InputEventAckWaiter await_update_in_child( |
| child_rwhv->GetRenderWidgetHost(), |
| base::BindRepeating(await_gesture_event_with_position, |
| blink::WebInputEvent::kGestureScrollUpdate, |
| child_rwhv, gfx::PointF(38, 38), |
| gfx::PointF(child_point_in_root))); |
| InputEventAckWaiter await_update_in_root( |
| rwhv_root->GetRenderWidgetHost(), |
| base::BindRepeating(await_gesture_event_with_position, |
| blink::WebInputEvent::kGestureScrollUpdate, |
| rwhv_root, gfx::PointF(child_point_in_root), |
| gfx::PointF(child_point_in_root))); |
| router->RouteMouseWheelEvent(rwhv_root, &scroll_event, ui::LatencyInfo()); |
| await_begin_in_child.Wait(); |
| await_update_in_child.Wait(); |
| await_update_in_root.Wait(); |
| thread_observer.Wait(); |
| } |
| |
| // Send mouse wheel update event to child. |
| { |
| scroll_event.phase = blink::WebMouseWheelEvent::kPhaseChanged; |
| InputEventAckWaiter await_update_in_child( |
| child_rwhv->GetRenderWidgetHost(), |
| base::BindRepeating(await_gesture_event_with_position, |
| blink::WebInputEvent::kGestureScrollUpdate, |
| child_rwhv, gfx::PointF(38, 58), |
| gfx::PointF(child_point_in_root))); |
| InputEventAckWaiter await_update_in_root( |
| rwhv_root->GetRenderWidgetHost(), |
| base::BindRepeating(await_gesture_event_with_position, |
| blink::WebInputEvent::kGestureScrollUpdate, |
| rwhv_root, gfx::PointF(child_point_in_root), |
| gfx::PointF(child_point_in_root))); |
| router->RouteMouseWheelEvent(rwhv_root, &scroll_event, ui::LatencyInfo()); |
| await_update_in_child.Wait(); |
| await_update_in_root.Wait(); |
| thread_observer.Wait(); |
| } |
| |
| #if !defined(OS_WIN) |
| { |
| ui::ScrollEvent fling_start(ui::ET_SCROLL_FLING_START, child_point_in_root, |
| ui::EventTimeForNow(), 0, 10, 0, 10, 0, 1); |
| UpdateEventRootLocation(&fling_start, rwhv_root); |
| |
| InputEventAckWaiter await_fling_start_in_child( |
| child_rwhv->GetRenderWidgetHost(), |
| base::BindRepeating(await_gesture_event_with_position, |
| blink::WebInputEvent::kGestureFlingStart, |
| child_rwhv, gfx::PointF(38, 78), |
| gfx::PointF(child_point_in_root))); |
| rwhv_root->OnScrollEvent(&fling_start); |
| await_fling_start_in_child.Wait(); |
| thread_observer.Wait(); |
| } |
| #endif |
| } |
| |
| // Ensure that a cross-process subframe with a touch-handler can receive touch |
| // events. |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| SubframeTouchEventRouting) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "/frame_tree/page_with_positioned_nested_frames.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| WebContentsImpl* contents = web_contents(); |
| FrameTreeNode* root = contents->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| GURL frame_url( |
| embedded_test_server()->GetURL("b.com", "/page_with_touch_handler.html")); |
| NavigateFrameToURL(root->child_at(0), frame_url); |
| |
| // Synchronize with the child and parent renderers to guarantee that the |
| // surface information required for event hit testing is ready. |
| WaitForHitTestDataOrChildSurfaceReady( |
| root->child_at(0)->current_frame_host()); |
| |
| // There's no intrinsic reason the following values can't be equal, but they |
| // aren't at present, and if they become the same this test will need to be |
| // updated to accommodate. |
| EXPECT_NE(cc::kTouchActionAuto, cc::kTouchActionNone); |
| |
| // Verify the child's input router is initially not set. The TouchStart event |
| // will trigger kTouchActionNone being sent back to the browser. |
| RenderWidgetHostImpl* child_render_widget_host = |
| root->child_at(0)->current_frame_host()->GetRenderWidgetHost(); |
| EXPECT_FALSE(child_render_widget_host->input_router() |
| ->AllowedTouchAction() |
| .has_value()); |
| |
| InputEventAckWaiter waiter(child_render_widget_host, |
| blink::WebInputEvent::kTouchStart); |
| |
| // Simulate touch event to sub-frame. |
| gfx::Point child_center(150, 150); |
| auto* rwhv = static_cast<RenderWidgetHostViewAura*>( |
| contents->GetRenderWidgetHostView()); |
| |
| // Wait until renderer's compositor thread is synced. |
| { |
| MainThreadFrameObserver observer(child_render_widget_host); |
| observer.Wait(); |
| } |
| |
| ui::TouchEvent touch_event( |
| ui::ET_TOUCH_PRESSED, child_center, ui::EventTimeForNow(), |
| ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, |
| /* pointer_id*/ 0, |
| /* radius_x */ 30.0f, |
| /* radius_y */ 30.0f, |
| /* force */ 0.0f)); |
| UpdateEventRootLocation(&touch_event, rwhv); |
| rwhv->OnTouchEvent(&touch_event); |
| waiter.Wait(); |
| { |
| MainThreadFrameObserver observer(child_render_widget_host); |
| observer.Wait(); |
| } |
| |
| // Verify touch handler in subframe was invoked. |
| std::string result; |
| EXPECT_TRUE(ExecuteScriptAndExtractString( |
| root->child_at(0), |
| "window.domAutomationController.send(getLastTouchEvent());", &result)); |
| EXPECT_EQ("touchstart", result); |
| |
| // Verify the presence of the touch handler in the child frame correctly |
| // propagates touch-action:none information back to the child's input router. |
| EXPECT_EQ(cc::kTouchActionNone, |
| child_render_widget_host->input_router()->AllowedTouchAction()); |
| } |
| |
| // This test verifies that the test in |
| // SitePerProcessHitTestBrowserTest.SubframeTouchEventRouting also works |
| // properly for the main frame. Prior to the CL in which this test is |
| // introduced, use of MainThreadFrameObserver in SubframeTouchEventRouting was |
| // not necessary since the touch events were handled on the main thread. Now |
| // they are handled on the compositor thread, hence the need to synchronize. |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| MainframeTouchEventRouting) { |
| GURL main_url( |
| embedded_test_server()->GetURL("/page_with_touch_handler.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| WebContentsImpl* contents = web_contents(); |
| FrameTreeNode* root = contents->GetFrameTree()->root(); |
| |
| // Synchronize with the renderers to guarantee that the |
| // surface information required for event hit testing is ready. |
| auto* rwhv = static_cast<RenderWidgetHostViewAura*>( |
| contents->GetRenderWidgetHostView()); |
| |
| // There's no intrinsic reason the following values can't be equal, but they |
| // aren't at present, and if they become the same this test will need to be |
| // updated to accommodate. |
| EXPECT_NE(cc::kTouchActionAuto, cc::kTouchActionNone); |
| |
| // Verify the main frame's input router is initially not set. The |
| // TouchStart event will trigger kTouchActionNone being sent back to the |
| // browser. |
| RenderWidgetHostImpl* render_widget_host = |
| root->current_frame_host()->GetRenderWidgetHost(); |
| EXPECT_FALSE( |
| render_widget_host->input_router()->AllowedTouchAction().has_value()); |
| |
| // Simulate touch event to sub-frame. |
| gfx::Point frame_center(150, 150); |
| |
| // Wait until renderer's compositor thread is synced. |
| { |
| auto observer = |
| std::make_unique<MainThreadFrameObserver>(render_widget_host); |
| observer->Wait(); |
| } |
| |
| ui::TouchEvent touch_event( |
| ui::ET_TOUCH_PRESSED, frame_center, ui::EventTimeForNow(), |
| ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, |
| /* pointer_id*/ 0, |
| /* radius_x */ 30.0f, |
| /* radius_y */ 30.0f, |
| /* force */ 0.0f)); |
| UpdateEventRootLocation(&touch_event, rwhv); |
| rwhv->OnTouchEvent(&touch_event); |
| { |
| auto observer = |
| std::make_unique<MainThreadFrameObserver>(render_widget_host); |
| observer->Wait(); |
| } |
| |
| // Verify touch handler in subframe was invoked. |
| std::string result; |
| EXPECT_TRUE(ExecuteScriptAndExtractString( |
| root, "window.domAutomationController.send(getLastTouchEvent());", |
| &result)); |
| EXPECT_EQ("touchstart", result); |
| |
| // Verify the presence of the touch handler in the child frame correctly |
| // propagates touch-action:none information back to the child's input router. |
| EXPECT_EQ(cc::kTouchActionNone, |
| render_widget_host->input_router()->AllowedTouchAction()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| SubframeGestureEventRouting) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "/frame_tree/page_with_positioned_nested_frames.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| FrameTreeNode* root = web_contents()->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| GURL frame_url( |
| embedded_test_server()->GetURL("b.com", "/page_with_click_handler.html")); |
| NavigateFrameToURL(root->child_at(0), frame_url); |
| auto* child_frame_host = root->child_at(0)->current_frame_host(); |
| |
| // Synchronize with the child and parent renderers to guarantee that the |
| // surface information required for event hit testing is ready. |
| WaitForHitTestDataOrChildSurfaceReady(child_frame_host); |
| |
| // There have been no GestureTaps sent yet. |
| { |
| std::string result; |
| EXPECT_TRUE(ExecuteScriptAndExtractString( |
| child_frame_host, |
| "window.domAutomationController.send(getClickStatus());", &result)); |
| EXPECT_EQ("0 clicks received", result); |
| } |
| |
| // Simulate touch sequence to send GestureTap to sub-frame. |
| SyntheticTapGestureParams params; |
| params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT; |
| gfx::Point center(150, 150); |
| params.position = gfx::PointF(center.x(), center.y()); |
| params.duration_ms = 100; |
| std::unique_ptr<SyntheticTapGesture> gesture(new SyntheticTapGesture(params)); |
| |
| RenderWidgetHostImpl* render_widget_host = |
| root->current_frame_host()->GetRenderWidgetHost(); |
| InputEventAckWaiter ack_waiter(child_frame_host->GetRenderWidgetHost(), |
| blink::WebInputEvent::kGestureTap); |
| |
| #if defined(USE_AURA) |
| // Allows the gesture events to go through under mash. |
| SystemEventRewriter::ScopedAllow scoped_allow(&event_rewriter_); |
| #endif |
| render_widget_host->QueueSyntheticGesture( |
| std::move(gesture), base::BindOnce([](SyntheticGesture::Result result) { |
| EXPECT_EQ(SyntheticGesture::GESTURE_FINISHED, result); |
| })); |
| |
| // We must wait for the kGestureTap ack to come back before querying the click |
| // handler in the subframe. |
| ack_waiter.Wait(); |
| |
| // Verify click handler in subframe was invoked |
| { |
| std::string result; |
| EXPECT_TRUE(ExecuteScriptAndExtractString( |
| child_frame_host, |
| "window.domAutomationController.send(getClickStatus());", &result)); |
| EXPECT_EQ("1 click received", result); |
| } |
| } |
| |
| namespace { |
| |
| // Defined here to be close to |
| // SitePerProcessHitTestBrowserTest.InputEventRouterGestureTargetQueueTest. |
| // Will wait for RenderWidgetHost's compositor thread to sync if one is given. |
| // Returns the unique_touch_id of the TouchStart. |
| uint32_t SendTouchTapWithExpectedTarget( |
| RenderWidgetHostViewBase* root_view, |
| const gfx::Point& touch_point, |
| RenderWidgetHostViewBase*& router_touch_target, |
| const RenderWidgetHostViewBase* expected_target, |
| RenderWidgetHostImpl* child_render_widget_host) { |
| auto* root_view_aura = static_cast<RenderWidgetHostViewAura*>(root_view); |
| if (child_render_widget_host != nullptr) { |
| MainThreadFrameObserver observer(child_render_widget_host); |
| observer.Wait(); |
| } |
| ui::TouchEvent touch_event_pressed( |
| ui::ET_TOUCH_PRESSED, touch_point, ui::EventTimeForNow(), |
| ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, |
| /* pointer_id*/ 0, |
| /* radius_x */ 30.0f, |
| /* radius_y */ 30.0f, |
| /* force */ 0.0f)); |
| UpdateEventRootLocation(&touch_event_pressed, root_view_aura); |
| InputEventAckWaiter waiter(expected_target->GetRenderWidgetHost(), |
| blink::WebInputEvent::kTouchStart); |
| root_view_aura->OnTouchEvent(&touch_event_pressed); |
| if (child_render_widget_host != nullptr) { |
| MainThreadFrameObserver observer(child_render_widget_host); |
| observer.Wait(); |
| } |
| waiter.Wait(); |
| EXPECT_EQ(expected_target, router_touch_target); |
| ui::TouchEvent touch_event_released( |
| ui::ET_TOUCH_RELEASED, touch_point, ui::EventTimeForNow(), |
| ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, |
| /* pointer_id*/ 0, |
| /* radius_x */ 30.0f, |
| /* radius_y */ 30.0f, |
| /* force */ 0.0f)); |
| UpdateEventRootLocation(&touch_event_released, root_view_aura); |
| root_view_aura->OnTouchEvent(&touch_event_released); |
| if (child_render_widget_host != nullptr) { |
| MainThreadFrameObserver observer(child_render_widget_host); |
| observer.Wait(); |
| } |
| EXPECT_EQ(nullptr, router_touch_target); |
| return touch_event_pressed.unique_event_id(); |
| } |
| |
| void SendGestureTapSequenceWithExpectedTarget( |
| RenderWidgetHostViewBase* root_view, |
| const gfx::Point& gesture_point, |
| RenderWidgetHostViewBase*& router_gesture_target, |
| const RenderWidgetHostViewBase* expected_target, |
| const uint32_t unique_touch_event_id) { |
| auto* root_view_aura = static_cast<RenderWidgetHostViewAura*>(root_view); |
| |
| ui::GestureEventDetails gesture_begin_details(ui::ET_GESTURE_BEGIN); |
| gesture_begin_details.set_device_type( |
| ui::GestureDeviceType::DEVICE_TOUCHSCREEN); |
| ui::GestureEvent gesture_begin_event( |
| gesture_point.x(), gesture_point.y(), 0, ui::EventTimeForNow(), |
| gesture_begin_details, unique_touch_event_id); |
| UpdateEventRootLocation(&gesture_begin_event, root_view_aura); |
| root_view_aura->OnGestureEvent(&gesture_begin_event); |
| |
| ui::GestureEventDetails gesture_tap_down_details(ui::ET_GESTURE_TAP_DOWN); |
| gesture_tap_down_details.set_device_type( |
| ui::GestureDeviceType::DEVICE_TOUCHSCREEN); |
| ui::GestureEvent gesture_tap_down_event( |
| gesture_point.x(), gesture_point.y(), 0, ui::EventTimeForNow(), |
| gesture_tap_down_details, unique_touch_event_id); |
| UpdateEventRootLocation(&gesture_tap_down_event, root_view_aura); |
| root_view_aura->OnGestureEvent(&gesture_tap_down_event); |
| EXPECT_EQ(expected_target, router_gesture_target); |
| |
| ui::GestureEventDetails gesture_show_press_details(ui::ET_GESTURE_SHOW_PRESS); |
| gesture_show_press_details.set_device_type( |
| ui::GestureDeviceType::DEVICE_TOUCHSCREEN); |
| ui::GestureEvent gesture_show_press_event( |
| gesture_point.x(), gesture_point.y(), 0, ui::EventTimeForNow(), |
| gesture_show_press_details, unique_touch_event_id); |
| UpdateEventRootLocation(&gesture_show_press_event, root_view_aura); |
| root_view_aura->OnGestureEvent(&gesture_show_press_event); |
| EXPECT_EQ(expected_target, router_gesture_target); |
| |
| ui::GestureEventDetails gesture_tap_details(ui::ET_GESTURE_TAP); |
| gesture_tap_details.set_device_type( |
| ui::GestureDeviceType::DEVICE_TOUCHSCREEN); |
| gesture_tap_details.set_tap_count(1); |
| ui::GestureEvent gesture_tap_event(gesture_point.x(), gesture_point.y(), 0, |
| ui::EventTimeForNow(), gesture_tap_details, |
| unique_touch_event_id); |
| UpdateEventRootLocation(&gesture_tap_event, root_view_aura); |
| root_view_aura->OnGestureEvent(&gesture_tap_event); |
| EXPECT_EQ(nullptr, router_gesture_target); |
| |
| ui::GestureEventDetails gesture_end_details(ui::ET_GESTURE_END); |
| gesture_end_details.set_device_type( |
| ui::GestureDeviceType::DEVICE_TOUCHSCREEN); |
| ui::GestureEvent gesture_end_event(gesture_point.x(), gesture_point.y(), 0, |
| ui::EventTimeForNow(), gesture_end_details, |
| unique_touch_event_id); |
| UpdateEventRootLocation(&gesture_end_event, root_view_aura); |
| root_view_aura->OnGestureEvent(&gesture_end_event); |
| EXPECT_EQ(nullptr, router_gesture_target); |
| } |
| |
| void SendTouchpadPinchSequenceWithExpectedTarget( |
| RenderWidgetHostViewBase* root_view, |
| const gfx::Point& gesture_point, |
| RenderWidgetHostViewBase*& router_touchpad_gesture_target, |
| RenderWidgetHostViewBase* expected_target) { |
| auto* root_view_aura = static_cast<RenderWidgetHostViewAura*>(root_view); |
| |
| ui::GestureEventDetails pinch_begin_details(ui::ET_GESTURE_PINCH_BEGIN); |
| pinch_begin_details.set_device_type(ui::GestureDeviceType::DEVICE_TOUCHPAD); |
| ui::GestureEvent pinch_begin(gesture_point.x(), gesture_point.y(), 0, |
| ui::EventTimeForNow(), pinch_begin_details); |
| UpdateEventRootLocation(&pinch_begin, root_view_aura); |
| TestInputEventObserver target_monitor(expected_target->GetRenderWidgetHost()); |
| InputEventAckWaiter waiter(expected_target->GetRenderWidgetHost(), |
| blink::WebInputEvent::kGesturePinchBegin); |
| root_view_aura->OnGestureEvent(&pinch_begin); |
| // If the expected target is not the root, then we should be doing async |
| // targeting first. So event dispatch should not happen synchronously. |
| // Validate that the expected target does not receive the event immediately in |
| // such cases. |
| // V2 surface layer hit testing cannot handle pointer-events: none elements |
| // yet, see https://crbug.com/841358. |
| if (root_view != expected_target && |
| !features::IsVizHitTestingSurfaceLayerEnabled()) { |
| EXPECT_FALSE(target_monitor.EventWasReceived()); |
| } |
| waiter.Wait(); |
| EXPECT_TRUE(target_monitor.EventWasReceived()); |
| EXPECT_EQ(expected_target, router_touchpad_gesture_target); |
| target_monitor.ResetEventsReceived(); |
| |
| ui::GestureEventDetails pinch_update_details(ui::ET_GESTURE_PINCH_UPDATE); |
| pinch_update_details.set_device_type(ui::GestureDeviceType::DEVICE_TOUCHPAD); |
| pinch_update_details.set_scale(1.23); |
| ui::GestureEvent pinch_update(gesture_point.x(), gesture_point.y(), 0, |
| ui::EventTimeForNow(), pinch_update_details); |
| UpdateEventRootLocation(&pinch_update, root_view_aura); |
| root_view_aura->OnGestureEvent(&pinch_update); |
| EXPECT_EQ(expected_target, router_touchpad_gesture_target); |
| EXPECT_TRUE(target_monitor.EventWasReceived()); |
| EXPECT_EQ(target_monitor.EventType(), |
| blink::WebInputEvent::kGesturePinchUpdate); |
| target_monitor.ResetEventsReceived(); |
| |
| ui::GestureEventDetails pinch_end_details(ui::ET_GESTURE_PINCH_END); |
| pinch_end_details.set_device_type(ui::GestureDeviceType::DEVICE_TOUCHPAD); |
| ui::GestureEvent pinch_end(gesture_point.x(), gesture_point.y(), 0, |
| ui::EventTimeForNow(), pinch_end_details); |
| UpdateEventRootLocation(&pinch_end, root_view_aura); |
| root_view_aura->OnGestureEvent(&pinch_end); |
| EXPECT_TRUE(target_monitor.EventWasReceived()); |
| EXPECT_EQ(target_monitor.EventType(), blink::WebInputEvent::kGesturePinchEnd); |
| EXPECT_EQ(nullptr, router_touchpad_gesture_target); |
| } |
| |
| #if !defined(OS_WIN) |
| // Sending touchpad fling events is not supported on Windows. |
| void SendTouchpadFlingSequenceWithExpectedTarget( |
| RenderWidgetHostViewBase* root_view, |
| const gfx::Point& gesture_point, |
| RenderWidgetHostViewBase*& router_wheel_target, |
| RenderWidgetHostViewBase* expected_target) { |
| auto* root_view_aura = static_cast<RenderWidgetHostViewAura*>(root_view); |
| |
| ui::ScrollEvent scroll_begin(ui::ET_SCROLL, gesture_point, |
| ui::EventTimeForNow(), 0, 1, 0, 1, 0, 2); |
| UpdateEventRootLocation(&scroll_begin, root_view_aura); |
| root_view_aura->OnScrollEvent(&scroll_begin); |
| |
| ui::ScrollEvent fling_start(ui::ET_SCROLL_FLING_START, gesture_point, |
| ui::EventTimeForNow(), 0, 1, 0, 1, 0, 1); |
| UpdateEventRootLocation(&fling_start, root_view_aura); |
| TestInputEventObserver target_monitor(expected_target->GetRenderWidgetHost()); |
| InputEventAckWaiter fling_start_waiter( |
| expected_target->GetRenderWidgetHost(), |
| blink::WebInputEvent::kGestureFlingStart); |
| InputMsgWatcher gestrue_scroll_end_waiter( |
| expected_target->GetRenderWidgetHost(), |
| blink::WebInputEvent::kGestureScrollEnd); |
| root_view_aura->OnScrollEvent(&fling_start); |
| // If the expected target is not the root, then we should be doing async |
| // targeting first. So event dispatch should not happen synchronously. |
| // Validate that the expected target does not receive the event immediately in |
| // such cases. |
| // When V2 surface layer hit testing is enabled, we should synchronously |
| // target the event to the child. |
| if (root_view != expected_target && |
| !features::IsVizHitTestingSurfaceLayerEnabled()) { |
| EXPECT_FALSE(target_monitor.EventWasReceived()); |
| } |
| fling_start_waiter.Wait(); |
| EXPECT_TRUE(target_monitor.EventWasReceived()); |
| EXPECT_EQ(expected_target, router_wheel_target); |
| target_monitor.ResetEventsReceived(); |
| |
| // Send a GFC event, the fling_controller will process the GFC and stop the |
| // fling by generating a wheel event with phaseEnded. The |
| // mouse_wheel_event_queue will process the wheel event and generate a GSE. |
| InputEventAckWaiter fling_cancel_waiter( |
| expected_target->GetRenderWidgetHost(), |
| blink::WebInputEvent::kGestureFlingCancel); |
| ui::ScrollEvent fling_cancel(ui::ET_SCROLL_FLING_CANCEL, gesture_point, |
| ui::EventTimeForNow(), 0, 1, 0, 1, 0, 1); |
| UpdateEventRootLocation(&fling_cancel, root_view_aura); |
| root_view_aura->OnScrollEvent(&fling_cancel); |
| // Since the fling velocity is small, sometimes the fling is over before |
| // sending the GFC event. |
| gestrue_scroll_end_waiter.GetAckStateWaitIfNecessary(); |
| fling_cancel_waiter.Wait(); |
| } |
| #endif // !defined(OS_WIN) |
| |
| } // anonymous namespace |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| InputEventRouterGestureTargetMapTest) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "/frame_tree/page_with_positioned_nested_frames.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| WebContentsImpl* contents = web_contents(); |
| FrameTreeNode* root = contents->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| GURL frame_url( |
| embedded_test_server()->GetURL("b.com", "/page_with_click_handler.html")); |
| NavigateFrameToURL(root->child_at(0), frame_url); |
| auto* child_frame_host = root->child_at(0)->current_frame_host(); |
| auto* rwhv_child = |
| static_cast<RenderWidgetHostViewBase*>(child_frame_host->GetView()); |
| |
| // Synchronize with the child and parent renderers to guarantee that the |
| // surface information required for event hit testing is ready. |
| WaitForHitTestDataOrChildSurfaceReady(child_frame_host); |
| |
| // All touches & gestures are sent to the main frame's view, and should be |
| // routed appropriately from there. |
| auto* rwhv_parent = static_cast<RenderWidgetHostViewBase*>( |
| contents->GetRenderWidgetHostView()); |
| |
| RenderWidgetHostInputEventRouter* router = contents->GetInputEventRouter(); |
| EXPECT_TRUE(router->touchscreen_gesture_target_map_.empty()); |
| EXPECT_EQ(nullptr, router->touchscreen_gesture_target_); |
| |
| // Send touch sequence to main-frame. |
| gfx::Point main_frame_point(25, 25); |
| uint32_t firstId = SendTouchTapWithExpectedTarget( |
| rwhv_parent, main_frame_point, router->touch_target_, rwhv_parent, |
| nullptr); |
| EXPECT_EQ(1u, router->touchscreen_gesture_target_map_.size()); |
| EXPECT_EQ(nullptr, router->touchscreen_gesture_target_); |
| |
| // Send touch sequence to child. |
| gfx::Point child_center(150, 150); |
| uint32_t secondId = SendTouchTapWithExpectedTarget( |
| rwhv_parent, child_center, router->touch_target_, rwhv_child, nullptr); |
| EXPECT_EQ(2u, router->touchscreen_gesture_target_map_.size()); |
| EXPECT_EQ(nullptr, router->touchscreen_gesture_target_); |
| |
| // Send another touch sequence to main frame. |
| uint32_t thirdId = SendTouchTapWithExpectedTarget( |
| rwhv_parent, main_frame_point, router->touch_target_, rwhv_parent, |
| nullptr); |
| EXPECT_EQ(3u, router->touchscreen_gesture_target_map_.size()); |
| EXPECT_EQ(nullptr, router->touchscreen_gesture_target_); |
| |
| // Send Gestures to clear GestureTargetQueue. |
| |
| // The first touch sequence should generate a GestureTapDown, sent to the |
| // main frame. |
| SendGestureTapSequenceWithExpectedTarget(rwhv_parent, main_frame_point, |
| router->touchscreen_gesture_target_, |
| rwhv_parent, firstId); |
| EXPECT_EQ(2u, router->touchscreen_gesture_target_map_.size()); |
| |
| // The second touch sequence should generate a GestureTapDown, sent to the |
| // child frame. |
| SendGestureTapSequenceWithExpectedTarget(rwhv_parent, child_center, |
| router->touchscreen_gesture_target_, |
| rwhv_child, secondId); |
| EXPECT_EQ(1u, router->touchscreen_gesture_target_map_.size()); |
| |
| // The third touch sequence should generate a GestureTapDown, sent to the |
| // main frame. |
| SendGestureTapSequenceWithExpectedTarget(rwhv_parent, main_frame_point, |
| router->touchscreen_gesture_target_, |
| rwhv_parent, thirdId); |
| EXPECT_EQ(0u, router->touchscreen_gesture_target_map_.size()); |
| } |
| |
| // TODO: Flaking test crbug.com/802827 |
| #if defined(OS_WIN) |
| #define MAYBE_InputEventRouterGesturePreventDefaultTargetMapTest \ |
| DISABLED_InputEventRouterGesturePreventDefaultTargetMapTest |
| #else |
| #define MAYBE_InputEventRouterGesturePreventDefaultTargetMapTest \ |
| InputEventRouterGesturePreventDefaultTargetMapTest |
| #endif |
| #if defined(USE_AURA) || defined(OS_ANDROID) |
| IN_PROC_BROWSER_TEST_P( |
| SitePerProcessHitTestBrowserTest, |
| MAYBE_InputEventRouterGesturePreventDefaultTargetMapTest) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "/frame_tree/page_with_positioned_nested_frames.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| WebContentsImpl* contents = web_contents(); |
| FrameTreeNode* root = contents->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| GURL frame_url(embedded_test_server()->GetURL( |
| "b.com", "/page_with_touch_start_default_prevented.html")); |
| NavigateFrameToURL(root->child_at(0), frame_url); |
| |
| auto* child_frame_host = root->child_at(0)->current_frame_host(); |
| RenderWidgetHostImpl* child_render_widget_host = |
| child_frame_host->GetRenderWidgetHost(); |
| auto* rwhv_child = |
| static_cast<RenderWidgetHostViewBase*>(child_frame_host->GetView()); |
| |
| // Synchronize with the child and parent renderers to guarantee that the |
| // surface information required for event hit testing is ready. |
| WaitForHitTestDataOrChildSurfaceReady(child_frame_host); |
| |
| // All touches & gestures are sent to the main frame's view, and should be |
| // routed appropriately from there. |
| auto* rwhv_parent = static_cast<RenderWidgetHostViewBase*>( |
| contents->GetRenderWidgetHostView()); |
| |
| RenderWidgetHostInputEventRouter* router = contents->GetInputEventRouter(); |
| EXPECT_TRUE(router->touchscreen_gesture_target_map_.empty()); |
| EXPECT_EQ(nullptr, router->touchscreen_gesture_target_); |
| |
| // Send touch sequence to main-frame. |
| gfx::Point main_frame_point(25, 25); |
| uint32_t firstId = SendTouchTapWithExpectedTarget( |
| rwhv_parent, main_frame_point, router->touch_target_, rwhv_parent, |
| child_render_widget_host); |
| EXPECT_EQ(1u, router->touchscreen_gesture_target_map_.size()); |
| EXPECT_EQ(nullptr, router->touchscreen_gesture_target_); |
| |
| // Send touch sequence to child. |
| gfx::Point child_center(150, 150); |
| SendTouchTapWithExpectedTarget(rwhv_parent, child_center, |
| router->touch_target_, rwhv_child, |
| child_render_widget_host); |
| EXPECT_EQ(1u, router->touchscreen_gesture_target_map_.size()); |
| EXPECT_EQ(nullptr, router->touchscreen_gesture_target_); |
| |
| // Send another touch sequence to main frame. |
| uint32_t thirdId = SendTouchTapWithExpectedTarget( |
| rwhv_parent, main_frame_point, router->touch_target_, rwhv_parent, |
| child_render_widget_host); |
| EXPECT_EQ(2u, router->touchscreen_gesture_target_map_.size()); |
| EXPECT_EQ(nullptr, router->touchscreen_gesture_target_); |
| |
| // Send Gestures to clear GestureTargetQueue. |
| |
| // The first touch sequence should generate a GestureTapDown, sent to the |
| // main frame. |
| SendGestureTapSequenceWithExpectedTarget(rwhv_parent, main_frame_point, |
| router->touchscreen_gesture_target_, |
| rwhv_parent, firstId); |
| EXPECT_EQ(1u, router->touchscreen_gesture_target_map_.size()); |
| |
| // The third touch sequence should generate a GestureTapDown, sent to the |
| // main frame. |
| SendGestureTapSequenceWithExpectedTarget(rwhv_parent, main_frame_point, |
| router->touchscreen_gesture_target_, |
| rwhv_parent, thirdId); |
| EXPECT_EQ(0u, router->touchscreen_gesture_target_map_.size()); |
| } |
| #endif // defined(USE_AURA) || defined(OS_ANDROID) |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| InputEventRouterTouchpadGestureTargetTest) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "/frame_tree/page_with_positioned_nested_frames.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| WebContentsImpl* contents = web_contents(); |
| FrameTreeNode* root = contents->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| GURL frame_url( |
| embedded_test_server()->GetURL("b.com", "/page_with_click_handler.html")); |
| NavigateFrameToURL(root->child_at(0), frame_url); |
| auto* child_frame_host = root->child_at(0)->current_frame_host(); |
| |
| // Synchronize with the child and parent renderers to guarantee that the |
| // surface information required for event hit testing is ready. |
| auto* rwhv_child = |
| static_cast<RenderWidgetHostViewBase*>(child_frame_host->GetView()); |
| WaitForHitTestDataOrChildSurfaceReady(child_frame_host); |
| |
| // All touches & gestures are sent to the main frame's view, and should be |
| // routed appropriately from there. |
| auto* rwhv_parent = static_cast<RenderWidgetHostViewBase*>( |
| contents->GetRenderWidgetHostView()); |
| |
| RenderWidgetHostInputEventRouter* router = contents->GetInputEventRouter(); |
| EXPECT_EQ(nullptr, router->touchpad_gesture_target_); |
| |
| // TODO(848050): If we send multiple touchpad pinch sequences to separate |
| // views and the timing of the acks are such that the begin ack of the second |
| // sequence arrives in the root before the end ack of the first sequence, we |
| // would produce an invalid gesture event sequence. For now, we wait for the |
| // root to receive the end ack before sending a pinch sequence to a different |
| // view. The root view should preserve validity of input event sequences |
| // when processing acks from multiple views, so that waiting here is not |
| // necessary. |
| auto wait_for_pinch_sequence_end = base::BindRepeating( |
| [](RenderWidgetHost* rwh) { |
| InputEventAckWaiter pinch_end_observer( |
| rwh, base::BindRepeating([](content::InputEventAckSource, |
| content::InputEventAckState, |
| const blink::WebInputEvent& event) { |
| return event.GetType() == |
| blink::WebGestureEvent::kGesturePinchEnd && |
| !static_cast<const blink::WebGestureEvent&>(event) |
| .NeedsWheelEvent(); |
| })); |
| pinch_end_observer.Wait(); |
| }, |
| rwhv_parent->GetRenderWidgetHost()); |
| |
| gfx::Point main_frame_point(25, 25); |
| gfx::Point child_center(150, 150); |
| |
| // Send touchpad pinch sequence to main-frame. |
| SendTouchpadPinchSequenceWithExpectedTarget(rwhv_parent, main_frame_point, |
| router->touchpad_gesture_target_, |
| rwhv_parent); |
| |
| wait_for_pinch_sequence_end.Run(); |
| |
| // Send touchpad pinch sequence to child. |
| SendTouchpadPinchSequenceWithExpectedTarget( |
| rwhv_parent, child_center, router->touchpad_gesture_target_, rwhv_child); |
| |
| wait_for_pinch_sequence_end.Run(); |
| |
| // Send another touchpad pinch sequence to main frame. |
| SendTouchpadPinchSequenceWithExpectedTarget(rwhv_parent, main_frame_point, |
| router->touchpad_gesture_target_, |
| rwhv_parent); |
| |
| #if !defined(OS_WIN) |
| // Sending touchpad fling events is not supported on Windows. |
| |
| // Send touchpad fling sequence to main-frame. |
| SendTouchpadFlingSequenceWithExpectedTarget( |
| rwhv_parent, main_frame_point, router->wheel_target_, rwhv_parent); |
| |
| // Send touchpad fling sequence to child. |
| SendTouchpadFlingSequenceWithExpectedTarget( |
| rwhv_parent, child_center, router->wheel_target_, rwhv_child); |
| |
| // Send another touchpad fling sequence to main frame. |
| SendTouchpadFlingSequenceWithExpectedTarget( |
| rwhv_parent, main_frame_point, router->wheel_target_, rwhv_parent); |
| #endif |
| } |
| |
| // Test that performing a touchpad pinch over an OOPIF offers the synthetic |
| // wheel events to the child and causes the page scale factor to change for |
| // the main frame (given that the child did not consume the wheel). |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| TouchpadPinchOverOOPIF) { |
| 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()); |
| |
| WebContentsImpl* contents = web_contents(); |
| FrameTreeNode* root = contents->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| GURL frame_url( |
| embedded_test_server()->GetURL("b.com", "/page_with_wheel_handler.html")); |
| NavigateFrameToURL(root->child_at(0), frame_url); |
| auto* child_frame_host = root->child_at(0)->current_frame_host(); |
| |
| auto* rwhv_child = |
| static_cast<RenderWidgetHostViewBase*>(child_frame_host->GetView()); |
| WaitForHitTestDataOrChildSurfaceReady(child_frame_host); |
| |
| auto* rwhv_parent = static_cast<RenderWidgetHostViewBase*>( |
| contents->GetRenderWidgetHostView()); |
| |
| RenderWidgetHostInputEventRouter* router = contents->GetInputEventRouter(); |
| EXPECT_EQ(nullptr, router->touchpad_gesture_target_); |
| |
| const float scale_factor = |
| render_frame_submission_observer.LastRenderFrameMetadata() |
| .page_scale_factor; |
| const gfx::Point point_in_child(gfx::ToCeiledInt(100 * scale_factor), |
| gfx::ToCeiledInt(100 * scale_factor)); |
| |
| content::TestPageScaleObserver scale_observer(shell()->web_contents()); |
| SendTouchpadPinchSequenceWithExpectedTarget(rwhv_parent, point_in_child, |
| router->touchpad_gesture_target_, |
| rwhv_child); |
| |
| // Ensure the child frame saw the wheel event. |
| bool default_prevented = false; |
| ASSERT_TRUE(content::ExecuteScriptAndExtractBool( |
| child_frame_host, |
| "handlerPromise.then(function(e) {" |
| " window.domAutomationController.send(e.defaultPrevented);" |
| "});", |
| &default_prevented)); |
| EXPECT_FALSE(default_prevented); |
| |
| scale_observer.WaitForPageScaleUpdate(); |
| } |
| |
| #endif // defined(USE_AURA) |
| |
| // Test that we can still perform a touchpad pinch gesture in the absence of viz |
| // hit test data without crashing. |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| TouchpadPinchWhenMissingHitTestDataDoesNotCrash) { |
| if (!features::IsVizHitTestingEnabled()) |
| return; |
| |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/frame_tree/page_with_positioned_frame.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| WebContentsImpl* contents = web_contents(); |
| FrameTreeNode* root = contents->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| // Even though we're sending the events to the root, we need an OOPIF so |
| // that hit testing doesn't short circuit. |
| EXPECT_EQ( |
| " Site A ------------ proxies for B\n" |
| " +--Site B ------- proxies for A\n" |
| "Where A = http://a.com/\n" |
| " B = http://baz.com/", |
| DepictFrameTree(root)); |
| |
| // Clobber the real hit test data once it comes in. |
| WaitForHitTestDataOrChildSurfaceReady(root->current_frame_host()); |
| ASSERT_TRUE(GetHostFrameSinkManager()); |
| viz::HostFrameSinkManager::DisplayHitTestQueryMap empty_hit_test_map; |
| viz::HostFrameSinkManagerTestApi(GetHostFrameSinkManager()) |
| .SetDisplayHitTestQuery(std::move(empty_hit_test_map)); |
| |
| const gfx::PointF point_in_root(1, 1); |
| SyntheticPinchGestureParams params; |
| params.gesture_source_type = SyntheticGestureParams::TOUCHPAD_INPUT; |
| params.scale_factor = 1.2f; |
| params.anchor = point_in_root; |
| |
| auto pinch_gesture = std::make_unique<SyntheticTouchpadPinchGesture>(params); |
| RenderWidgetHostImpl* render_widget_host = |
| root->current_frame_host()->GetRenderWidgetHost(); |
| |
| base::RunLoop run_loop; |
| render_widget_host->QueueSyntheticGesture( |
| std::move(pinch_gesture), |
| base::BindOnce( |
| [](base::OnceClosure quit_closure, SyntheticGesture::Result result) { |
| std::move(quit_closure).Run(); |
| }, |
| run_loop.QuitClosure())); |
| run_loop.Run(); |
| } |
| |
| // Tests that performing a touchpad double-tap zoom over an OOPIF offers the |
| // synthetic wheel event to the child. |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| TouchpadDoubleTapZoomOverOOPIF) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "/frame_tree/page_with_positioned_frame.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| WebContentsImpl* contents = web_contents(); |
| |
| WebPreferences prefs = contents->GetRenderViewHost()->GetWebkitPreferences(); |
| prefs.double_tap_to_zoom_enabled = true; |
| contents->GetRenderViewHost()->UpdateWebkitPreferences(prefs); |
| |
| RenderFrameSubmissionObserver render_frame_submission_observer( |
| shell()->web_contents()); |
| |
| FrameTreeNode* root = contents->GetFrameTree()->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| GURL frame_url( |
| embedded_test_server()->GetURL("b.com", "/page_with_wheel_handler.html")); |
| NavigateFrameToURL(root->child_at(0), frame_url); |
| auto* child_frame_host = root->child_at(0)->current_frame_host(); |
| |
| WaitForHitTestDataOrChildSurfaceReady(child_frame_host); |
| |
| auto* root_view = static_cast<RenderWidgetHostViewBase*>( |
| contents->GetRenderWidgetHostView()); |
| RenderWidgetHostViewBase* child_view = static_cast<RenderWidgetHostViewBase*>( |
| child_frame_host->GetRenderWidgetHost()->GetView()); |
| |
| const float scale_factor = |
| render_frame_submission_observer.LastRenderFrameMetadata() |
| .page_scale_factor; |
| gfx::PointF point_in_screen(child_view->GetViewBounds().CenterPoint()); |
| point_in_screen.Scale(scale_factor); |
| // It might seem weird to not also scale the root_view's view bounds, but |
| // since the origin should be unaffected by page scale we don't need to. |
| const gfx::PointF root_location( |
| point_in_screen - root_view->GetViewBounds().OffsetFromOrigin()); |
| |
| RenderWidgetHostInputEventRouter* router = contents->GetInputEventRouter(); |
| |
| blink::WebGestureEvent double_tap_zoom( |
| blink::WebInputEvent::kGestureDoubleTap, |
| blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests(), |
| blink::kWebGestureDeviceTouchpad); |
| double_tap_zoom.SetPositionInWidget(root_location); |
| double_tap_zoom.SetPositionInScreen(point_in_screen); |
| double_tap_zoom.data.tap.tap_count = 1; |
| double_tap_zoom.SetNeedsWheelEvent(true); |
| |
| content::TestPageScaleObserver scale_observer(shell()->web_contents()); |
| |
| router->RouteGestureEvent(root_view, &double_tap_zoom, |
| ui::LatencyInfo(ui::SourceEventType::WHEEL)); |
| |
| // Ensure the child frame saw the wheel event. |
| bool default_prevented = false; |
| ASSERT_TRUE(content::ExecuteScriptAndExtractBool( |
| child_frame_host, |
| "handlerPromise.then(function(e) {" |
| " window.domAutomationController.send(e.defaultPrevented);" |
| "});", |
| &default_prevented)); |
| EXPECT_FALSE(default_prevented); |
| |
| // TODO(mcnee): Support double-tap zoom gesture for OOPIFs. For now, we |
| // only test that any scale change still happens in the main frame when |
| // the double tap is performed over the OOPIF. Once this works with OOPIFs, |
| // we should be able to test that the new scale is based on the target |
| // rect of the element in the OOPIF. https://crbug.com/758348 |
| scale_observer.WaitForPageScaleUpdate(); |
| } |
| |
| // A WebContentsDelegate to capture ContextMenu creation events. |
| class ContextMenuObserverDelegate : public WebContentsDelegate { |
| public: |
| ContextMenuObserverDelegate() |
| : context_menu_created_(false), |
| message_loop_runner_(new MessageLoopRunner) {} |
| |
| ~ContextMenuObserverDelegate() override {} |
| |
| bool HandleContextMenu(content::RenderFrameHost* render_frame_host, |
| const content::ContextMenuParams& params) override { |
| context_menu_created_ = true; |
| menu_params_ = params; |
| message_loop_runner_->Quit(); |
| return true; |
| } |
| |
| ContextMenuParams getParams() { return menu_params_; } |
| |
| void Wait() { |
| if (!context_menu_created_) |
| message_loop_runner_->Run(); |
| context_menu_created_ = false; |
| } |
| |
| private: |
| bool context_menu_created_; |
| ContextMenuParams menu_params_; |
| |
| // The MessageLoopRunner used to spin the message loop. |
| scoped_refptr<MessageLoopRunner> message_loop_runner_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ContextMenuObserverDelegate); |
| }; |
| |
| // Helper function to run the CreateContextMenuTest in either normal |
| // or high DPI mode. |
| void CreateContextMenuTestHelper( |
| 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)); |
| |
| 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 = 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()); |
| |
| RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>( |
| child_node->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| // Ensure that the child process renderer is ready to have input events |
| // routed to it. This happens when the browser process has received |
| // updated compositor surfaces from both renderer processes. |
| WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host()); |
| |
| // A WebContentsDelegate to listen for the ShowContextMenu message. |
| ContextMenuObserverDelegate context_menu_delegate; |
| shell->web_contents()->SetDelegate(&context_menu_delegate); |
| |
| RenderWidgetHostInputEventRouter* router = |
| static_cast<WebContentsImpl*>(shell->web_contents()) |
| ->GetInputEventRouter(); |
| |
| float scale_factor = |
| render_frame_submission_observer.LastRenderFrameMetadata() |
| .page_scale_factor; |
| |
| gfx::Rect root_bounds = root_view->GetViewBounds(); |
| gfx::Rect bounds = rwhv_child->GetViewBounds(); |
| |
| gfx::Point point( |
| gfx::ToCeiledInt((bounds.x() - root_bounds.x() + 5) * scale_factor), |
| gfx::ToCeiledInt((bounds.y() - root_bounds.y() + 5) * scale_factor)); |
| |
| // Target right-click event to child frame. |
| blink::WebMouseEvent click_event( |
| blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| click_event.button = blink::WebPointerProperties::Button::kRight; |
| SetWebEventPositions(&click_event, point, root_view); |
| click_event.click_count = 1; |
| router->RouteMouseEvent(root_view, &click_event, ui::LatencyInfo()); |
| |
| // We also need a MouseUp event, needed by Windows. |
| click_event.SetType(blink::WebInputEvent::kMouseUp); |
| SetWebEventPositions(&click_event, point, root_view); |
| router->RouteMouseEvent(root_view, &click_event, ui::LatencyInfo()); |
| |
| context_menu_delegate.Wait(); |
| |
| ContextMenuParams params = context_menu_delegate.getParams(); |
| |
| EXPECT_NEAR(point.x(), params.x, kHitTestTolerance); |
| EXPECT_NEAR(point.y(), params.y, kHitTestTolerance); |
| } |
| |
| #if defined(OS_ANDROID) || defined(OS_WIN) |
| // High DPI tests don't work properly on Android, which has fixed scale factor. |
| // Windows is disabled because of https://crbug.com/545547. |
| #define MAYBE_CreateContextMenuTest DISABLED_CreateContextMenuTest |
| #else |
| #define MAYBE_CreateContextMenuTest CreateContextMenuTest |
| #endif |
| |
| // Test that a mouse right-click to an out-of-process iframe causes a context |
| // menu to be generated with the correct screen position. |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| MAYBE_CreateContextMenuTest) { |
| CreateContextMenuTestHelper(shell(), embedded_test_server()); |
| } |
| |
| // Test that a mouse right-click to an out-of-process iframe causes a context |
| // menu to be generated with the correct screen position on a screen with |
| // non-default scale factor. |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest, |
| MAYBE_CreateContextMenuTest) { |
| CreateContextMenuTestHelper(shell(), embedded_test_server()); |
| } |
| |
| // Test that clicking a select element in an out-of-process iframe creates |
| // a popup menu in the correct position. |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, PopupMenuTest) { |
| GURL main_url( |
| embedded_test_server()->GetURL("/cross_site_iframe_factory.html?a(a)")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| FrameTreeNode* root = web_contents()->GetFrameTree()->root(); |
| |
| RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| FrameTreeNode* child_node = root->child_at(0); |
| GURL site_url(embedded_test_server()->GetURL( |
| "baz.com", "/site_isolation/page-with-select.html")); |
| NavigateFrameToURL(child_node, site_url); |
| |
| web_contents()->SendScreenRects(); |
| |
| WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host()); |
| |
| RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>( |
| child_node->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| EXPECT_NE(shell()->web_contents()->GetSiteInstance(), |
| child_node->current_frame_host()->GetSiteInstance()); |
| |
| scoped_refptr<ShowWidgetMessageFilter> filter = new ShowWidgetMessageFilter(); |
| child_node->current_frame_host()->GetProcess()->AddFilter(filter.get()); |
| |
| // Target left-click event to child frame. |
| blink::WebMouseEvent click_event( |
| blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| click_event.button = blink::WebPointerProperties::Button::kLeft; |
| SetWebEventPositions(&click_event, gfx::Point(15, 15), rwhv_root); |
| click_event.click_count = 1; |
| rwhv_child->ProcessMouseEvent(click_event, ui::LatencyInfo()); |
| |
| // Dismiss the popup. |
| SetWebEventPositions(&click_event, gfx::Point(1, 1), rwhv_root); |
| rwhv_child->ProcessMouseEvent(click_event, ui::LatencyInfo()); |
| |
| filter->Wait(); |
| gfx::Rect popup_rect = filter->last_initial_rect(); |
| if (IsUseZoomForDSFEnabled()) { |
| ScreenInfo screen_info; |
| shell()->web_contents()->GetRenderWidgetHostView()->GetScreenInfo( |
| &screen_info); |
| popup_rect = gfx::ScaleToRoundedRect(popup_rect, |
| 1 / screen_info.device_scale_factor); |
| } |
| #if defined(OS_MACOSX) || defined(OS_ANDROID) |
| // On Mac and Android we receive the coordinates before they are transformed, |
| // so they are still relative to the out-of-process iframe origin. |
| EXPECT_EQ(popup_rect.x(), 9); |
| EXPECT_EQ(popup_rect.y(), 9); |
| #else |
| EXPECT_EQ(popup_rect.x() - rwhv_root->GetViewBounds().x(), 354); |
| EXPECT_EQ(popup_rect.y() - rwhv_root->GetViewBounds().y(), 94); |
| #endif |
| |
| #if defined(OS_LINUX) |
| // Verify click-and-drag selection of popups still works on Linux with |
| // OOPIFs enabled. This is only necessary to test on Aura because Mac and |
| // Android use native widgets. Windows does not support this as UI |
| // convention (it requires separate clicks to open the menu and select an |
| // option). See https://crbug.com/703191. |
| int process_id = child_node->current_frame_host()->GetProcess()->GetID(); |
| filter->Reset(); |
| RenderWidgetHostInputEventRouter* router = |
| static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetInputEventRouter(); |
| // Re-open the select element. |
| SetWebEventPositions(&click_event, gfx::Point(360, 90), rwhv_root); |
| click_event.click_count = 1; |
| router->RouteMouseEvent(rwhv_root, &click_event, ui::LatencyInfo()); |
| |
| filter->Wait(); |
| |
| RenderWidgetHostViewAura* popup_view = static_cast<RenderWidgetHostViewAura*>( |
| RenderWidgetHost::FromID(process_id, filter->last_routing_id()) |
| ->GetView()); |
| // The IO thread posts to ViewMsg_ShowWidget handlers in both the message |
| // filter above and the WebContents, which initializes the popup's view. |
| // It is possible for this code to execute before the WebContents handler, |
| // in which case OnMouseEvent would be called on an uninitialized RWHVA. |
| // This loop ensures that the initialization completes before proceeding. |
| while (!popup_view->window()) { |
| base::RunLoop loop; |
| base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, |
| loop.QuitClosure()); |
| loop.Run(); |
| } |
| |
| RenderWidgetHostMouseEventMonitor popup_monitor( |
| popup_view->GetRenderWidgetHost()); |
| |
| // Next send a mouse up directly targeting the first option, simulating a |
| // drag. This requires a ui::MouseEvent because it tests behavior that is |
| // above RWH input event routing. |
| ui::MouseEvent mouse_up_event(ui::ET_MOUSE_RELEASED, gfx::Point(10, 5), |
| gfx::Point(10, 5), ui::EventTimeForNow(), |
| ui::EF_LEFT_MOUSE_BUTTON, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| UpdateEventRootLocation(&mouse_up_event, rwhv_root); |
| popup_view->OnMouseEvent(&mouse_up_event); |
| |
| // This verifies that the popup actually received the event, and it wasn't |
| // diverted to a different RenderWidgetHostView due to mouse capture. |
| EXPECT_TRUE(popup_monitor.EventWasReceived()); |
| #endif // defined(OS_LINUX) |
| } |
| |
| // Test that clicking a select element in a nested out-of-process iframe creates |
| // a popup menu in the correct position, even if the top-level page repositions |
| // its out-of-process iframe. This verifies that screen positioning information |
| // is propagating down the frame tree correctly. |
| #if defined(OS_ANDROID) |
| // Surface-based hit testing and coordinate translation is not yet avaiable on |
| // Android. |
| #define MAYBE_NestedPopupMenuTest DISABLED_NestedPopupMenuTest |
| #else |
| // Times out frequently. https://crbug.com/599730. |
| #define MAYBE_NestedPopupMenuTest DISABLED_NestedPopupMenuTest |
| #endif |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| MAYBE_NestedPopupMenuTest) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "/cross_site_iframe_factory.html?a(b(c))")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| FrameTreeNode* root = web_contents()->GetFrameTree()->root(); |
| |
| RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| web_contents()->SendScreenRects(); |
| |
| // For clarity, we are labeling the frame tree nodes as: |
| // - root_node |
| // \-> b_node (out-of-process from root and c_node) |
| // \-> c_node (out-of-process from root and b_node) |
| |
| content::TestNavigationObserver navigation_observer(shell()->web_contents()); |
| FrameTreeNode* b_node = root->child_at(0); |
| FrameTreeNode* c_node = b_node->child_at(0); |
| GURL site_url(embedded_test_server()->GetURL( |
| "baz.com", "/site_isolation/page-with-select.html")); |
| NavigateFrameToURL(c_node, site_url); |
| |
| RenderWidgetHostViewBase* rwhv_c_node = |
| static_cast<RenderWidgetHostViewBase*>( |
| c_node->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| EXPECT_NE(shell()->web_contents()->GetSiteInstance(), |
| c_node->current_frame_host()->GetSiteInstance()); |
| |
| scoped_refptr<ShowWidgetMessageFilter> filter = new ShowWidgetMessageFilter(); |
| c_node->current_frame_host()->GetProcess()->AddFilter(filter.get()); |
| |
| // Target left-click event to child frame. |
| blink::WebMouseEvent click_event( |
| blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| click_event.button = blink::WebPointerProperties::Button::kLeft; |
| SetWebEventPositions(&click_event, gfx::Point(15, 15), rwhv_root); |
| click_event.click_count = 1; |
| rwhv_c_node->ProcessMouseEvent(click_event, ui::LatencyInfo()); |
| |
| // Prompt the WebContents to dismiss the popup by clicking elsewhere. |
| SetWebEventPositions(&click_event, gfx::Point(1, 1), rwhv_root); |
| rwhv_c_node->ProcessMouseEvent(click_event, ui::LatencyInfo()); |
| |
| filter->Wait(); |
| |
| gfx::Rect popup_rect = filter->last_initial_rect(); |
| |
| #if defined(OS_MACOSX) |
| EXPECT_EQ(popup_rect.x(), 9); |
| EXPECT_EQ(popup_rect.y(), 9); |
| #else |
| EXPECT_EQ(popup_rect.x() - rwhv_root->GetViewBounds().x(), 354); |
| EXPECT_EQ(popup_rect.y() - rwhv_root->GetViewBounds().y(), 154); |
| #endif |
| |
| // Save the screen rect for b_node. Since it updates asynchronously from |
| // the script command that changes it, we need to wait for it to change |
| // before attempting to create the popup widget again. |
| gfx::Rect last_b_node_bounds_rect = |
| b_node->current_frame_host()->GetView()->GetViewBounds(); |
| |
| std::string script = |
| "var iframe = document.querySelector('iframe');" |
| "iframe.style.position = 'absolute';" |
| "iframe.style.left = 150;" |
| "iframe.style.top = 150;"; |
| EXPECT_TRUE(ExecuteScript(root, script)); |
| |
| filter->Reset(); |
| |
| // Busy loop to wait for b_node's screen rect to get updated. There |
| // doesn't seem to be any better way to find out when this happens. |
| while (last_b_node_bounds_rect.x() == |
| b_node->current_frame_host()->GetView()->GetViewBounds().x() && |
| last_b_node_bounds_rect.y() == |
| b_node->current_frame_host()->GetView()->GetViewBounds().y()) { |
| base::RunLoop run_loop; |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout()); |
| run_loop.Run(); |
| } |
| |
| click_event.button = blink::WebPointerProperties::Button::kLeft; |
| SetWebEventPositions(&click_event, gfx::Point(15, 15), rwhv_root); |
| click_event.click_count = 1; |
| rwhv_c_node->ProcessMouseEvent(click_event, ui::LatencyInfo()); |
| |
| SetWebEventPositions(&click_event, gfx::Point(1, 1), rwhv_root); |
| rwhv_c_node->ProcessMouseEvent(click_event, ui::LatencyInfo()); |
| |
| filter->Wait(); |
| |
| popup_rect = filter->last_initial_rect(); |
| |
| #if defined(OS_MACOSX) |
| EXPECT_EQ(popup_rect.x(), 9); |
| EXPECT_EQ(popup_rect.y(), 9); |
| #else |
| EXPECT_EQ(popup_rect.x() - rwhv_root->GetViewBounds().x(), 203); |
| EXPECT_EQ(popup_rect.y() - rwhv_root->GetViewBounds().y(), 248); |
| #endif |
| } |
| |
| // Verify that scrolling the main frame correctly updates the position to |
| // a nested child frame. See issue https://crbug.com/878703 for more |
| // information. |
| // On Mac and Android, the reported menu coordinates are relative to the |
| // OOPIF, and its screen position is computed later, so this test isn't |
| // relevant on those platforms. |
| // TODO(crbug.com/889002): This test is flaky. |
| #if !defined(OS_ANDROID) && !defined(OS_MACOSX) |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, |
| DISABLED_ScrolledNestedPopupMenuTest) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/frame_tree/page_with_tall_positioned_frame.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| FrameTreeNode* root = web_contents()->GetFrameTree()->root(); |
| FrameTreeNode* child_node = root->child_at(0); |
| |
| GURL child_url(embedded_test_server()->GetURL( |
| "b.com", "/frame_tree/page_with_positioned_frame.html")); |
| NavigateFrameToURL(child_node, child_url); |
| |
| FrameTreeNode* grandchild_node = child_node->child_at(0); |
| |
| GURL grandchild_url(embedded_test_server()->GetURL( |
| "c.com", "/site_isolation/page-with-select.html")); |
| NavigateFrameToURL(grandchild_node, grandchild_url); |
| |
| WaitForHitTestDataOrChildSurfaceReady(grandchild_node->current_frame_host()); |
| |
| EXPECT_EQ( |
| " Site A ------------ proxies for B C\n" |
| " +--Site B ------- proxies for A C\n" |
| " +--Site C -- proxies for A B\n" |
| "Where A = http://a.com/\n" |
| " B = http://b.com/\n" |
| " C = http://c.com/", |
| DepictFrameTree(root)); |
| |
| RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| RenderWidgetHostViewBase* rwhv_grandchild = |
| static_cast<RenderWidgetHostViewBase*>( |
| grandchild_node->current_frame_host() |
| ->GetRenderWidgetHost() |
| ->GetView()); |
| |
| scoped_refptr<ShowWidgetMessageFilter> filter = new ShowWidgetMessageFilter(); |
| grandchild_node->current_frame_host()->GetProcess()->AddFilter(filter.get()); |
| |
| // Target left-click event to the select element in the innermost frame. |
| DispatchMouseEventAndWaitUntilDispatch(web_contents(), rwhv_grandchild, |
| gfx::PointF(15, 15), rwhv_grandchild, |
| gfx::PointF(15, 15)); |
| |
| // Prompt the WebContents to dismiss the popup by clicking elsewhere. |
| DispatchMouseEventAndWaitUntilDispatch(web_contents(), rwhv_grandchild, |
| gfx::PointF(2, 2), rwhv_grandchild, |
| gfx::PointF(2, 2)); |
| filter->Wait(); |
| |
| // This test isn't verifying correctness of these coordinates, this is just |
| // to ensure that they change after scroll. |
| gfx::Rect unscrolled_popup_rect = filter->last_initial_rect(); |
| gfx::Rect initial_grandchild_view_bounds = rwhv_grandchild->GetViewBounds(); |
| |
| // Scroll the main frame. |
| EXPECT_TRUE(ExecuteScript(root, "window.scrollTo(0, 20);")); |
| |
| // Wait until the OOPIF positions have been updated in the browser process. |
| while (true) { |
| base::RunLoop run_loop; |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout()); |
| run_loop.Run(); |
| if (initial_grandchild_view_bounds.y() == |
| rwhv_grandchild->GetViewBounds().y() + 20) |
| break; |
| } |
| |
| filter->Reset(); |
| // This sends the message directly to the rwhv_grandchild, avoiding using |
| // the helper methods, to avert a race condition with the surfaces or |
| // HitTestRegions needing to update post-scroll. The event won't hit test |
| // correctly if it gets sent before a fresh compositor frame is received. |
| 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; |
| down_event.SetPositionInWidget(15, 15); |
| rwhv_grandchild->ProcessMouseEvent(down_event, ui::LatencyInfo()); |
| |
| // Dismiss the popup again. This time there is no need to worry about |
| // compositor frame updates because it is sufficient to send the click to |
| // the root frame. |
| DispatchMouseEventAndWaitUntilDispatch(web_contents(), rwhv_root, |
| gfx::PointF(1, 1), rwhv_root, |
| gfx::PointF(1, 1)); |
| filter->Wait(); |
| EXPECT_EQ(unscrolled_popup_rect.y(), filter->last_initial_rect().y() + 20); |
| } |
| #endif // !defined(OS_ANDROID) |
| |
| #if defined(USE_AURA) |
| class SitePerProcessGestureHitTestBrowserTest |
| : public SitePerProcessHitTestBrowserTest { |
| public: |
| SitePerProcessGestureHitTestBrowserTest() {} |
| |
| // This functions simulates a sequence of events that are typical of a |
| // gesture pinch at |position|. We need this since machinery in the event |
| // codepath will require GesturePinch* to be enclosed in |
| // GestureScrollBegin/End, and since RenderWidgetHostInputEventRouter needs |
| // both the preceding touch events, as well as GestureTapDown, in order to |
| // correctly target the subsequent gesture event stream. The minimum stream |
| // required to trigger the correct behaviours is represented here, but could |
| // be expanded to include additional events such as one or more |
| // GestureScrollUpdate and GesturePinchUpdate events. |
| void SendPinchBeginEndSequence(RenderWidgetHostViewAura* rwhva, |
| const gfx::Point& position, |
| RenderWidgetHost* expected_target_rwh) { |
| DCHECK(rwhva); |
| // Use full version of constructor with radius, angle and force since it |
| // will crash in the renderer otherwise. |
| ui::TouchEvent touch_pressed( |
| ui::ET_TOUCH_PRESSED, position, ui::EventTimeForNow(), |
| ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, |
| /* pointer_id*/ 0, |
| /* radius_x */ 1.0f, |
| /* radius_y */ 1.0f, |
| /* force */ 1.0f)); |
| UpdateEventRootLocation(&touch_pressed, rwhva); |
| InputEventAckWaiter waiter(expected_target_rwh, |
| blink::WebInputEvent::kTouchStart); |
| rwhva->OnTouchEvent(&touch_pressed); |
| waiter.Wait(); |
| |
| ui::GestureEventDetails gesture_tap_down_details(ui::ET_GESTURE_TAP_DOWN); |
| gesture_tap_down_details.set_device_type( |
| ui::GestureDeviceType::DEVICE_TOUCHSCREEN); |
| ui::GestureEvent gesture_tap_down( |
| position.x(), position.y(), 0, ui::EventTimeForNow(), |
| gesture_tap_down_details, touch_pressed.unique_event_id()); |
| UpdateEventRootLocation(&gesture_tap_down, rwhva); |
| rwhva->OnGestureEvent(&gesture_tap_down); |
| |
| ui::GestureEventDetails gesture_scroll_begin_details( |
| ui::ET_GESTURE_SCROLL_BEGIN); |
| gesture_scroll_begin_details.set_device_type( |
| ui::GestureDeviceType::DEVICE_TOUCHSCREEN); |
| gesture_scroll_begin_details.set_touch_points(2); |
| ui::GestureEvent gesture_scroll_begin( |
| position.x(), position.y(), 0, ui::EventTimeForNow(), |
| gesture_scroll_begin_details, touch_pressed.unique_event_id()); |
| UpdateEventRootLocation(&gesture_scroll_begin, rwhva); |
| rwhva->OnGestureEvent(&gesture_scroll_begin); |
| |
| ui::GestureEventDetails gesture_pinch_begin_details( |
| ui::ET_GESTURE_PINCH_BEGIN); |
| gesture_pinch_begin_details.set_device_type( |
| ui::GestureDeviceType::DEVICE_TOUCHSCREEN); |
| ui::GestureEvent gesture_pinch_begin( |
| position.x(), position.y(), 0, ui::EventTimeForNow(), |
| gesture_pinch_begin_details, touch_pressed.unique_event_id()); |
| UpdateEventRootLocation(&gesture_pinch_begin, rwhva); |
| rwhva->OnGestureEvent(&gesture_pinch_begin); |
| |
| ui::GestureEventDetails gesture_pinch_end_details(ui::ET_GESTURE_PINCH_END); |
| gesture_pinch_end_details.set_device_type( |
| ui::GestureDeviceType::DEVICE_TOUCHSCREEN); |
| ui::GestureEvent gesture_pinch_end( |
| position.x(), position.y(), 0, ui::EventTimeForNow(), |
| gesture_pinch_end_details, touch_pressed.unique_event_id()); |
| UpdateEventRootLocation(&gesture_pinch_end, rwhva); |
| rwhva->OnGestureEvent(&gesture_pinch_end); |
| |
| ui::GestureEventDetails gesture_scroll_end_details( |
| ui::ET_GESTURE_SCROLL_END); |
| gesture_scroll_end_details.set_device_type( |
| ui::GestureDeviceType::DEVICE_TOUCHSCREEN); |
| ui::GestureEvent gesture_scroll_end( |
| position.x(), position.y(), 0, ui::EventTimeForNow(), |
| gesture_scroll_end_details, touch_pressed.unique_event_id()); |
| UpdateEventRootLocation(&gesture_scroll_end, rwhva); |
| rwhva->OnGestureEvent(&gesture_scroll_end); |
| |
| // TouchActionFilter is reset when a touch event sequence ends, so in order |
| // to preserve the touch action set by TouchStart, we end release touch |
| // after pinch gestures. |
| ui::TouchEvent touch_released( |
| ui::ET_TOUCH_RELEASED, position, ui::EventTimeForNow(), |
| ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, |
| /* pointer_id*/ 0, |
| /* radius_x */ 1.0f, |
| /* radius_y */ 1.0f, |
| /* force */ 1.0f)); |
| InputEventAckWaiter touch_released_waiter(expected_target_rwh, |
| blink::WebInputEvent::kTouchEnd); |
| rwhva->OnTouchEvent(&touch_released); |
| touch_released_waiter.Wait(); |
| } |
| |
| void SetupRootAndChild() { |
| GURL main_url(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b)")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| FrameTreeNode* root_node = |
| static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree() |
| ->root(); |
| FrameTreeNode* child_node = root_node->child_at(0); |
| |
| rwhv_child_ = static_cast<RenderWidgetHostViewBase*>( |
| child_node->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| rwhva_root_ = static_cast<RenderWidgetHostViewAura*>( |
| shell()->web_contents()->GetRenderWidgetHostView()); |
| |
| WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host()); |
| |
| rwhi_child_ = child_node->current_frame_host()->GetRenderWidgetHost(); |
| rwhi_root_ = root_node->current_frame_host()->GetRenderWidgetHost(); |
| } |
| |
| void SubframeGesturePinchTestHelper(const std::string& url, |
| bool reset_root_touch_action) { |
| 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_node = |
| static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree() |
| ->root(); |
| ASSERT_EQ(1U, root_node->child_count()); |
| |
| FrameTreeNode* child_node = root_node->child_at(0); |
| GURL b_url(embedded_test_server()->GetURL("b.com", url)); |
| NavigateFrameToURL(child_node, b_url); |
| |
| ASSERT_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_node)); |
| |
| rwhv_child_ = static_cast<RenderWidgetHostViewBase*>( |
| child_node->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| rwhva_root_ = static_cast<RenderWidgetHostViewAura*>( |
| shell()->web_contents()->GetRenderWidgetHostView()); |
| |
| WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host()); |
| |
| MainThreadFrameObserver observer(rwhv_child_->GetRenderWidgetHost()); |
| observer.Wait(); |
| |
| rwhi_child_ = child_node->current_frame_host()->GetRenderWidgetHost(); |
| rwhi_root_ = root_node->current_frame_host()->GetRenderWidgetHost(); |
| |
| TestInputEventObserver root_frame_monitor(rwhi_root_); |
| TestInputEventObserver child_frame_monitor(rwhi_child_); |
| |
| gfx::Rect bounds = rwhv_child_->GetViewBounds(); |
| bounds.Offset(gfx::Point() - rwhva_root_->GetViewBounds().origin()); |
| |
| // The pinch gesture will always sent to the root frame even if the fingers |
| // are targeting the iframe. In this case, the test should not crash. |
| if (reset_root_touch_action) { |
| static_cast<InputRouterImpl*>( |
| static_cast<RenderWidgetHostImpl*>(rwhva_root_->GetRenderWidgetHost()) |
| ->input_router()) |
| ->ForceResetTouchActionForTest(); |
| } |
| SendPinchBeginEndSequence(rwhva_root_, bounds.CenterPoint(), rwhi_child_); |
| |
| if (reset_root_touch_action) |
| return; |
| |
| // Verify that root-RWHI gets nothing. |
| EXPECT_FALSE(root_frame_monitor.EventWasReceived()); |
| // Verify that child-RWHI gets TS/GTD/GSB/GPB/GPE/GSE/TE. |
| EXPECT_EQ(blink::WebInputEvent::kTouchStart, |
| child_frame_monitor.events_received()[0]); |
| EXPECT_EQ(blink::WebInputEvent::kGestureTapDown, |
| child_frame_monitor.events_received()[1]); |
| EXPECT_EQ(blink::WebInputEvent::kGestureScrollBegin, |
| child_frame_monitor.events_received()[2]); |
| EXPECT_EQ(blink::WebInputEvent::kGesturePinchBegin, |
| child_frame_monitor.events_received()[3]); |
| EXPECT_EQ(blink::WebInputEvent::kGesturePinchEnd, |
| child_frame_monitor.events_received()[4]); |
| EXPECT_EQ(blink::WebInputEvent::kGestureScrollEnd, |
| child_frame_monitor.events_received()[5]); |
| EXPECT_EQ(blink::WebInputEvent::kTouchEnd, |
| child_frame_monitor.events_received()[6]); |
| |
| // Verify that the pinch gestures are consumed by browser. |
| EXPECT_EQ(InputEventAckSource::BROWSER, |
| child_frame_monitor.events_acked()[3]); |
| EXPECT_EQ(InputEventAckSource::BROWSER, |
| child_frame_monitor.events_acked()[4]); |
| } |
| |
| protected: |
| RenderWidgetHostViewBase* rwhv_child_; |
| RenderWidgetHostViewAura* rwhva_root_; |
| RenderWidgetHostImpl* rwhi_child_; |
| RenderWidgetHostImpl* rwhi_root_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(SitePerProcessGestureHitTestBrowserTest); |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessGestureHitTestBrowserTest, |
| SubframeGesturePinchGoesToMainFrame) { |
| SetupRootAndChild(); |
| |
| TestInputEventObserver root_frame_monitor(rwhi_root_); |
| TestInputEventObserver child_frame_monitor(rwhi_child_); |
| |
| // Need child rect in main frame coords. |
| gfx::Rect bounds = rwhv_child_->GetViewBounds(); |
| bounds.Offset(gfx::Point() - rwhva_root_->GetViewBounds().origin()); |
| SendPinchBeginEndSequence(rwhva_root_, bounds.CenterPoint(), rwhi_child_); |
| |
| // Verify root-RWHI gets GSB/GPB/GPE/GSE. |
| EXPECT_TRUE(root_frame_monitor.EventWasReceived()); |
| EXPECT_EQ(blink::WebInputEvent::kGestureScrollBegin, |
| root_frame_monitor.events_received()[0]); |
| EXPECT_EQ(blink::WebInputEvent::kGesturePinchBegin, |
| root_frame_monitor.events_received()[1]); |
| EXPECT_EQ(blink::WebInputEvent::kGesturePinchEnd, |
| root_frame_monitor.events_received()[2]); |
| EXPECT_EQ(blink::WebInputEvent::kGestureScrollEnd, |
| root_frame_monitor.events_received()[3]); |
| |
| // Verify child-RWHI gets TS/TE, GTD/GSB/GSE. |
| EXPECT_TRUE(child_frame_monitor.EventWasReceived()); |
| EXPECT_EQ(blink::WebInputEvent::kTouchStart, |
| child_frame_monitor.events_received()[0]); |
| EXPECT_EQ(blink::WebInputEvent::kGestureTapDown, |
| child_frame_monitor.events_received()[1]); |
| EXPECT_EQ(blink::WebInputEvent::kGestureScrollBegin, |
| child_frame_monitor.events_received()[2]); |
| EXPECT_EQ(blink::WebInputEvent::kGestureScrollEnd, |
| child_frame_monitor.events_received()[3]); |
| EXPECT_EQ(blink::WebInputEvent::kTouchEnd, |
| child_frame_monitor.events_received()[4]); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessGestureHitTestBrowserTest, |
| MainframeGesturePinchGoesToMainFrame) { |
| SetupRootAndChild(); |
| |
| TestInputEventObserver root_frame_monitor(rwhi_root_); |
| TestInputEventObserver child_frame_monitor(rwhi_child_); |
| |
| // Need child rect in main frame coords. |
| gfx::Rect bounds = rwhv_child_->GetViewBounds(); |
| bounds.Offset(gfx::Point() - rwhva_root_->GetViewBounds().origin()); |
| |
| gfx::Point main_frame_point(bounds.origin()); |
| main_frame_point += gfx::Vector2d(-5, -5); |
| SendPinchBeginEndSequence(rwhva_root_, main_frame_point, rwhi_root_); |
| |
| // Verify root-RWHI gets TS/TE/GTD/GSB/GPB/GPE/GSE. |
| EXPECT_TRUE(root_frame_monitor.EventWasReceived()); |
| EXPECT_EQ(blink::WebInputEvent::kTouchStart, |
| root_frame_monitor.events_received()[0]); |
| EXPECT_EQ(blink::WebInputEvent::kGestureTapDown, |
| root_frame_monitor.events_received()[1]); |
| EXPECT_EQ(blink::WebInputEvent::kGestureScrollBegin, |
| root_frame_monitor.events_received()[2]); |
| EXPECT_EQ(blink::WebInputEvent::kGesturePinchBegin, |
| root_frame_monitor.events_received()[3]); |
| EXPECT_EQ(blink::WebInputEvent::kGesturePinchEnd, |
| root_frame_monitor.events_received()[4]); |
| EXPECT_EQ(blink::WebInputEvent::kGestureScrollEnd, |
| root_frame_monitor.events_received()[5]); |
| EXPECT_EQ(blink::WebInputEvent::kTouchEnd, |
| root_frame_monitor.events_received()[6]); |
| |
| // Verify child-RWHI gets no events. |
| EXPECT_FALSE(child_frame_monitor.EventWasReceived()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessGestureHitTestBrowserTest, |
| SubframeGesturePinchDeniedBySubframeTouchAction) { |
| SubframeGesturePinchTestHelper("/div_with_touch_action_none.html", false); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessGestureHitTestBrowserTest, |
| SubframeGesturePinchNoCrash) { |
| SubframeGesturePinchTestHelper("/div_with_touch_action_auto.html", true); |
| } |
| #endif // defined(USE_AURA) |
| |
| // Test that MouseDown and MouseUp to the same coordinates do not result in |
| // different coordinates after routing. See bug https://crbug.com/670253. |
| #if defined(OS_ANDROID) |
| // Android uses fixed scale factor, which makes this test unnecessary. |
| #define MAYBE_MouseClickWithNonIntegerScaleFactor \ |
| DISABLED_MouseClickWithNonIntegerScaleFactor |
| #else |
| #define MAYBE_MouseClickWithNonIntegerScaleFactor \ |
| MouseClickWithNonIntegerScaleFactor |
| #endif |
| IN_PROC_BROWSER_TEST_P(SitePerProcessNonIntegerScaleFactorHitTestBrowserTest, |
| MAYBE_MouseClickWithNonIntegerScaleFactor) { |
| GURL initial_url(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), initial_url)); |
| |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree() |
| ->root(); |
| |
| RenderWidgetHostViewBase* rwhv = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| RenderWidgetHostInputEventRouter* router = |
| static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetInputEventRouter(); |
| |
| // Create listener for input events. |
| RenderWidgetHostMouseEventMonitor event_monitor( |
| root->current_frame_host()->GetRenderWidgetHost()); |
| |
| blink::WebMouseEvent mouse_event( |
| blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| mouse_event.button = blink::WebPointerProperties::Button::kLeft; |
| SetWebEventPositions(&mouse_event, gfx::Point(75, 75), rwhv); |
| mouse_event.click_count = 1; |
| event_monitor.ResetEventReceived(); |
| router->RouteMouseEvent(rwhv, &mouse_event, ui::LatencyInfo()); |
| |
| EXPECT_TRUE(event_monitor.EventWasReceived()); |
| gfx::Point mouse_down_coords = |
| gfx::Point(event_monitor.event().PositionInWidget().x, |
| event_monitor.event().PositionInWidget().y); |
| event_monitor.ResetEventReceived(); |
| |
| mouse_event.SetType(blink::WebInputEvent::kMouseUp); |
| SetWebEventPositions(&mouse_event, gfx::Point(75, 75), rwhv); |
| router->RouteMouseEvent(rwhv, &mouse_event, ui::LatencyInfo()); |
| |
| EXPECT_TRUE(event_monitor.EventWasReceived()); |
| EXPECT_EQ(mouse_down_coords.x(), event_monitor.event().PositionInWidget().x); |
| // The transform from browser to renderer is (2, 35) in DIP. When we |
| // scale that to pixels, it's (3, 53). Note that 35 * 1.5 should be 52.5, |
| // so we already lost precision there in the transform from draw quad. |
| EXPECT_NEAR(mouse_down_coords.y(), event_monitor.event().PositionInWidget().y, |
| kHitTestTolerance); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessNonIntegerScaleFactorHitTestBrowserTest, |
| NestedSurfaceHitTestTest) { |
| NestedSurfaceHitTestTestHelper(shell(), embedded_test_server()); |
| } |
| |
| // Verify RenderWidgetHostInputEventRouter can successfully hit test |
| // a MouseEvent and route it to a clipped OOPIF. |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, HitTestClippedFrame) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "/frame_tree/page_with_positioned_clipped_iframe.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree() |
| ->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| RenderWidgetHostInputEventRouter* router = |
| web_contents()->GetInputEventRouter(); |
| |
| EXPECT_EQ( |
| " Site A ------------ proxies for B\n" |
| " +--Site B ------- proxies for A\n" |
| "Where A = http://127.0.0.1/\n" |
| " B = http://baz.com/", |
| DepictFrameTree(root)); |
| |
| FrameTreeNode* child_node = root->child_at(0); |
| RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>( |
| child_node->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host()); |
| |
| RenderWidgetHostMouseEventMonitor root_monitor( |
| root->current_frame_host()->GetRenderWidgetHost()); |
| RenderWidgetHostMouseEventMonitor child_monitor( |
| child_node->current_frame_host()->GetRenderWidgetHost()); |
| |
| gfx::PointF point_in_root(25, 25); |
| gfx::PointF point_in_child(100, 100); |
| |
| 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; |
| SetWebEventPositions(&down_event, point_in_root, rwhv_root); |
| |
| blink::WebMouseEvent up_event( |
| blink::WebInputEvent::kMouseUp, blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| up_event.button = blink::WebPointerProperties::Button::kLeft; |
| up_event.click_count = 1; |
| SetWebEventPositions(&up_event, point_in_root, rwhv_root); |
| |
| // Target at root. |
| RouteMouseEventAndWaitUntilDispatch(router, rwhv_root, rwhv_root, |
| &down_event); |
| EXPECT_TRUE(root_monitor.EventWasReceived()); |
| EXPECT_FALSE(child_monitor.EventWasReceived()); |
| EXPECT_NEAR(25, root_monitor.event().PositionInWidget().x, kHitTestTolerance); |
| EXPECT_NEAR(25, root_monitor.event().PositionInWidget().y, kHitTestTolerance); |
| |
| root_monitor.ResetEventReceived(); |
| child_monitor.ResetEventReceived(); |
| RouteMouseEventAndWaitUntilDispatch(router, rwhv_root, rwhv_root, &up_event); |
| EXPECT_TRUE(root_monitor.EventWasReceived()); |
| EXPECT_FALSE(child_monitor.EventWasReceived()); |
| EXPECT_NEAR(25, root_monitor.event().PositionInWidget().x, kHitTestTolerance); |
| EXPECT_NEAR(25, root_monitor.event().PositionInWidget().y, kHitTestTolerance); |
| |
| // Target at child. |
| root_monitor.ResetEventReceived(); |
| child_monitor.ResetEventReceived(); |
| SetWebEventPositions(&down_event, point_in_child, rwhv_root); |
| SetWebEventPositions(&up_event, point_in_child, rwhv_root); |
| RouteMouseEventAndWaitUntilDispatch(router, rwhv_root, rwhv_child, |
| &down_event); |
| // In surface layer hit testing, we should not query client asynchronously. |
| EXPECT_FALSE(root_monitor.EventWasReceived()); |
| EXPECT_TRUE(child_monitor.EventWasReceived()); |
| EXPECT_NEAR(90, child_monitor.event().PositionInWidget().x, |
| kHitTestTolerance); |
| EXPECT_NEAR(100, child_monitor.event().PositionInWidget().y, |
| kHitTestTolerance); |
| |
| root_monitor.ResetEventReceived(); |
| child_monitor.ResetEventReceived(); |
| RouteMouseEventAndWaitUntilDispatch(router, rwhv_root, rwhv_child, &up_event); |
| // We should reuse the target for mouse up. |
| EXPECT_FALSE(root_monitor.EventWasReceived()); |
| EXPECT_TRUE(child_monitor.EventWasReceived()); |
| EXPECT_TRUE(child_monitor.EventWasReceived()); |
| EXPECT_NEAR(90, child_monitor.event().PositionInWidget().x, |
| kHitTestTolerance); |
| EXPECT_NEAR(100, child_monitor.event().PositionInWidget().y, |
| kHitTestTolerance); |
| } |
| |
| // Verify InputTargetClient works within an OOPIF process. |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, HitTestNestedFrames) { |
| GURL main_url(embedded_test_server()->GetURL( |
| "/frame_tree/page_with_positioned_nested_frames.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree() |
| ->root(); |
| ASSERT_EQ(1U, root->child_count()); |
| |
| EXPECT_EQ( |
| " Site A ------------ proxies for B C\n" |
| " +--Site B ------- proxies for A C\n" |
| " +--Site C -- proxies for A B\n" |
| "Where A = http://127.0.0.1/\n" |
| " B = http://a.com/\n" |
| " C = http://baz.com/", |
| DepictFrameTree(root)); |
| |
| FrameTreeNode* child_node = root->child_at(0); |
| FrameTreeNode* grandchild_node = child_node->child_at(0); |
| RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>( |
| child_node->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| RenderWidgetHostViewBase* rwhv_grandchild = |
| static_cast<RenderWidgetHostViewBase*>( |
| grandchild_node->current_frame_host() |
| ->GetRenderWidgetHost() |
| ->GetView()); |
| |
| WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host()); |
| WaitForHitTestDataOrChildSurfaceReady(grandchild_node->current_frame_host()); |
| |
| // Create two points to hit test: One in the child of the main frame, and |
| // one in the frame nested within that. The hit test request is sent to the |
| // child's renderer. |
| gfx::PointF point_in_child(1, 1); |
| gfx::PointF point_in_nested_child(5, 5); |
| rwhv_grandchild->TransformPointToCoordSpaceForView( |
| point_in_nested_child, rwhv_child, &point_in_nested_child); |
| |
| { |
| base::RunLoop run_loop; |
| viz::FrameSinkId received_frame_sink_id; |
| gfx::PointF returned_point; |
| base::Closure quit_closure = |
| content::GetDeferredQuitTaskForRunLoop(&run_loop); |
| DCHECK_NE(child_node->current_frame_host()->GetInputTargetClient(), |
| nullptr); |
| child_node->current_frame_host()->GetInputTargetClient()->FrameSinkIdAt( |
| point_in_child, 0, |
| base::BindLambdaForTesting( |
| [&](const viz::FrameSinkId& id, const gfx::PointF& point) { |
| received_frame_sink_id = id; |
| returned_point = point; |
| quit_closure.Run(); |
| })); |
| content::RunThisRunLoop(&run_loop); |
| // |point_in_child| should hit test to the view for |child_node|. |
| ASSERT_EQ(rwhv_child->GetFrameSinkId(), received_frame_sink_id); |
| ASSERT_EQ(gfx::PointF(1, 1), returned_point); |
| } |
| |
| { |
| base::RunLoop run_loop; |
| viz::FrameSinkId received_frame_sink_id; |
| gfx::PointF returned_point; |
| base::Closure quit_closure = |
| content::GetDeferredQuitTaskForRunLoop(&run_loop); |
| DCHECK_NE(child_node->current_frame_host()->GetInputTargetClient(), |
| nullptr); |
| child_node->current_frame_host()->GetInputTargetClient()->FrameSinkIdAt( |
| point_in_nested_child, 0, |
| base::BindLambdaForTesting( |
| [&](const viz::FrameSinkId& id, const gfx::PointF& point) { |
| received_frame_sink_id = id; |
| returned_point = point; |
| quit_closure.Run(); |
| })); |
| content::RunThisRunLoop(&run_loop); |
| // |point_in_nested_child| should hit test to |rwhv_grandchild|. |
| ASSERT_EQ(rwhv_grandchild->GetFrameSinkId(), received_frame_sink_id); |
| EXPECT_NEAR(returned_point.x(), 5, kHitTestTolerance); |
| EXPECT_NEAR(returned_point.y(), 5, kHitTestTolerance); |
| } |
| } |
| |
| class SitePerProcessHitTestDataGenerationBrowserTest |
| : public SitePerProcessHitTestBrowserTest { |
| public: |
| SitePerProcessHitTestDataGenerationBrowserTest() {} |
| |
| protected: |
| // Load the page |host_name| and retrieve the hit test data from HitTestQuery. |
| std::vector<viz::AggregatedHitTestRegion> SetupAndGetHitTestData( |
| const std::string& host_name, |
| unsigned skipped_child = -1) { |
| GURL main_url(embedded_test_server()->GetURL(host_name)); |
| EXPECT_TRUE(NavigateToURL(shell(), main_url)); |
| |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree() |
| ->root(); |
| |
| RenderWidgetHostViewBase* rwhv_root = |
| static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| for (unsigned i = 0; i < root->child_count(); i++) { |
| // Child with pointer-events: none property will never submit a hit test |
| // region in /2 hit testing. |
| if (i != skipped_child) { |
| WaitForHitTestDataOrChildSurfaceReady( |
| root->child_at(i)->current_frame_host()); |
| } |
| } |
| |
| HitTestRegionObserver observer(rwhv_root->GetRootFrameSinkId()); |
| observer.WaitForHitTestData(); |
| |
| device_scale_factor_ = rwhv_root->GetDeviceScaleFactor(); |
| DCHECK_GT(device_scale_factor_, 0); |
| |
| auto hit_test_data = observer.GetHitTestData(); |
| MaybeStripHitTestData(&hit_test_data); |
| return hit_test_data; |
| } |
| |
| // Strip the ClientRoot frame sink id from |hit_test_data| when Window |
| // Service is used because tests are written without considering it and |
| // using a constant number index to access the data of the interested frame. |
| // Related to http://crbug.com/895029. |
| // Note the stripped data has wrong child count and should only be used to |
| // verify test expectations. |
| void MaybeStripHitTestData( |
| std::vector<viz::AggregatedHitTestRegion>* hit_test_data) { |
| if (!features::IsUsingWindowService()) |
| return; |
| |
| // There must be at least two frame sink ids: one root and one ClientRoot. |
| ASSERT_GE(hit_test_data->size(), 2u); |
| hit_test_data->erase(hit_test_data->begin() + 1); |
| } |
| |
| float current_device_scale_factor() const { return device_scale_factor_; } |
| |
| gfx::QuadF TransformRectToQuadF(const gfx::Rect& rect, |
| const gfx::Transform& transform, |
| bool use_scale_factor = true) { |
| gfx::Rect scaled_rect = |
| use_scale_factor ? gfx::ScaleToEnclosingRect(rect, device_scale_factor_, |
| device_scale_factor_) |
| : rect; |
| gfx::PointF p1(scaled_rect.origin()); |
| gfx::PointF p2(scaled_rect.top_right()); |
| gfx::PointF p3(scaled_rect.bottom_right()); |
| gfx::PointF p4(scaled_rect.bottom_left()); |
| transform.TransformPoint(&p1); |
| transform.TransformPoint(&p2); |
| transform.TransformPoint(&p3); |
| transform.TransformPoint(&p4); |
| return gfx::QuadF(p1, p2, p3, p4); |
| } |
| |
| gfx::QuadF TransformRectToQuadF( |
| const viz::AggregatedHitTestRegion& hit_test_region) { |
| return TransformRectToQuadF(hit_test_region.rect, |
| hit_test_region.transform(), false); |
| } |
| |
| bool ApproximatelyEqual(const gfx::PointF& p1, const gfx::PointF& p2) const { |
| return std::abs(p1.x() - p2.x()) <= 1 && std::abs(p1.y() - p2.y()) <= 1; |
| } |
| |
| bool ApproximatelyEqual(const gfx::QuadF& quad, |
| const gfx::QuadF& other) const { |
| return ApproximatelyEqual(quad.p1(), other.p1()) && |
| ApproximatelyEqual(quad.p2(), other.p2()) && |
| ApproximatelyEqual(quad.p3(), other.p3()) && |
| ApproximatelyEqual(quad.p4(), other.p4()); |
| } |
| |
| gfx::Rect AxisAlignedLayoutRectFromHitTest( |
| const viz::AggregatedHitTestRegion& hit_test_region) { |
| DCHECK(hit_test_region.transform().Preserves2dAxisAlignment()); |
| gfx::RectF rect(hit_test_region.rect); |
| hit_test_region.transform().TransformRect(&rect); |
| return gfx::ToEnclosingRect(rect); |
| } |
| |
| public: |
| static const uint32_t kFastHitTestFlags; |
| static const uint32_t kSlowHitTestFlags; |
| float device_scale_factor_; |
| }; |
| |
| const uint32_t |
| SitePerProcessHitTestDataGenerationBrowserTest::kFastHitTestFlags = |
| viz::HitTestRegionFlags::kHitTestMine | |
| viz::HitTestRegionFlags::kHitTestChildSurface | |
| viz::HitTestRegionFlags::kHitTestMouse | |
| viz::HitTestRegionFlags::kHitTestTouch; |
| |
| const uint32_t |
| SitePerProcessHitTestDataGenerationBrowserTest::kSlowHitTestFlags = |
| SitePerProcessHitTestDataGenerationBrowserTest::kFastHitTestFlags | |
| viz::HitTestRegionFlags::kHitTestAsk; |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestDataGenerationBrowserTest, |
| TransformedOOPIF) { |
| if (!features::IsVizHitTestingEnabled()) |
| return; |
| auto hit_test_data = |
| SetupAndGetHitTestData("/frame_tree/page_with_transformed_iframe.html"); |
| float device_scale_factor = current_device_scale_factor(); |
| |
| // Compute screen space transform for iframe element. |
| gfx::Transform expected_transform; |
| gfx::Transform translate; |
| expected_transform.RotateAboutZAxis(-45); |
| translate.Translate(-100 * device_scale_factor, -100 * device_scale_factor); |
| expected_transform.PreconcatTransform(translate); |
| |
| DCHECK(hit_test_data.size() >= 3); |
| // The iframe element in main page is transformed and also clips the content |
| // of the subframe, so we expect to do slow path hit testing in this case. |
| // TODO(sunxd): We should do fast path hit testing in this case. See |
| // https://crbug.com/851507. |
| EXPECT_TRUE(ApproximatelyEqual( |
| TransformRectToQuadF(gfx::Rect(100, 100), expected_transform), |
| TransformRectToQuadF(hit_test_data[2]))); |
| EXPECT_EQ(kSlowHitTestFlags, hit_test_data[2].flags); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestDataGenerationBrowserTest, |
| ClippedOOPIFFastPath) { |
| if (!features::IsVizHitTestingEnabled()) |
| return; |
| auto hit_test_data = |
| SetupAndGetHitTestData("/frame_tree/page_with_clipped_iframe.html"); |
| float device_scale_factor = current_device_scale_factor(); |
| gfx::Transform expected_transform; |
| // In V1 hit testing or V2 hit testing slow path, we expected unclipped iframe |
| // bounds in its own space. |
| gfx::Rect original_region(200, 200); |
| gfx::Rect expected_transformed_region = gfx::ScaleToEnclosingRect( |
| original_region, device_scale_factor, device_scale_factor); |
| |
| uint32_t expected_flags = kFastHitTestFlags; |
| // Clip2 has overflow: visible property, so it does not apply clip to iframe. |
| // Clip1 and clip3 all preserve 2d axis alignment, so we should allow fast |
| // path hit testing for the iframe in V2 hit testing. |
| // When VizDisplayCompositor is enabled, HitTestDataProviderDrawQuad will |
| // override LTHI's hit test data. |
| if (features::IsVizHitTestingDrawQuadEnabled()) { |
| // In V1 hit testing, we expect slow path and the submitted region should be |
| // equivalent to the unclipped iframe bounds. |
| expected_flags = kSlowHitTestFlags; |
| } else if (features::IsVizHitTestingSurfaceLayerEnabled()) { |
| // In V2 hit testing fast path, we expect precise clipped iframe bounds in |
| // its own space. |
| expected_transformed_region = gfx::ScaleToEnclosingRect( |
| gfx::Rect(100, 100), device_scale_factor, device_scale_factor); |
| } |
| |
| // Apart from the iframe, it also contains data for root and main frame. |
| DCHECK(hit_test_data.size() >= 3); |
| EXPECT_TRUE(expected_transformed_region.ApproximatelyEqual( |
| AxisAlignedLayoutRectFromHitTest(hit_test_data[2]), |
| gfx::ToRoundedInt(device_scale_factor) + 2)); |
| EXPECT_TRUE( |
| expected_transform.ApproximatelyEqual(hit_test_data[2].transform())); |
| EXPECT_EQ(expected_flags, hit_test_data[2].flags); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestDataGenerationBrowserTest, |
| RotatedClippedOOPIF) { |
| if (!features::IsVizHitTestingEnabled()) |
| return; |
| auto hit_test_data = SetupAndGetHitTestData( |
| "/frame_tree/page_with_rotated_clipped_iframe.html"); |
| float device_scale_factor = current_device_scale_factor(); |
| // +-Root |
| // +---clip1 |
| // +-----clip2 rotateZ(45) |
| // +-------clip3 rotateZ(-45) |
| // +---------iframe |
| // |
| // +----------------300px--------------+ |
| // |\ | |
| // | \ | |
| // | \ 100px |
| // |- x --\ | |
| // | / | |
| // +-----------------------------------+ |
| // |
| // Clipped region: x=100/sqrt(2), y=100. |
| gfx::Transform expected_transform; |
| gfx::Rect expected_region = gfx::ScaleToEnclosingRect( |
| gfx::Rect(200, 200), device_scale_factor, device_scale_factor); |
| if (!features::IsVizHitTestingDrawQuadEnabled()) { |
| expected_region = gfx::ScaleToEnclosingRect( |
| gfx::Rect(100 / 1.414, 100), device_scale_factor, device_scale_factor); |
| } |
| |
| // Compute screen space transform for iframe element, since clip2 is rotated |
| // and also clips the iframe, we expect to do slow path hit test on the |
| // iframe. |
| DCHECK(hit_test_data.size() >= 3); |
| EXPECT_TRUE(expected_region.ApproximatelyEqual(hit_test_data[2].rect, |
| 1 + device_scale_factor)); |
| EXPECT_TRUE( |
| expected_transform.ApproximatelyEqual(hit_test_data[2].transform())); |
| EXPECT_EQ(kSlowHitTestFlags, hit_test_data[2].flags); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestDataGenerationBrowserTest, |
| ClippedRotatedOOPIF) { |
| if (!features::IsVizHitTestingEnabled()) |
| return; |
| auto hit_test_data = SetupAndGetHitTestData( |
| "/frame_tree/page_with_clipped_rotated_iframe.html"); |
| float device_scale_factor = current_device_scale_factor(); |
| // +-Root |
| // +---clip1 |
| // +---------iframe rotateZ(45deg) |
| // |
| // There are actually 2 clips applied to surface layer, in root space they |
| // are: |
| // bounding box of clip1: rect 0, 0 300x100, transform = identity; |
| // bounding box of iframe itself: rect -100*sqrt(2), 0 200*sqrt(2)x200*sqrt(2) |
| // transform: rotateZ(45). |
| // In root space the two clips accumulates to: |
| // rect 0, 0 100*sqrt(2)x100, transform=identity |
| // Transform this to layer's local space, the clip rect is: |
| // rect 0, -100/sqrt(2) (100+100/sqrt(2))x(100/sqrt(2)) |
| // So the intersected visible layer rect is: |
| // rect 0, 0, (100+100/sqrt(2)), 100/sqrt(2). |
| // +----------------300px--------------+ |
| // |\ | |
| // | \ | |
| // | \x 100px |
| // | / \ | |
| // | /y \ | |
| // +-----------------------------------+ |
| gfx::Transform expected_transform; |
| expected_transform.RotateAboutZAxis(-45); |
| gfx::Rect expected_region1 = gfx::ScaleToEnclosingRect( |
| gfx::Rect(200, 200), device_scale_factor, device_scale_factor); |
| gfx::Rect expected_region2; |
| if (features::IsVizHitTestingSurfaceLayerEnabled()) { |
| // The clip tree built by BlinkGenPropertyTrees is different from that build |
| // by cc. While it does not affect correctness of hit testing, the hit test |
| // region with kHitTestAsk will have a different size due to the change of |
| // accumulated clips. |
| expected_region1 = gfx::ScaleToEnclosingRect( |
| gfx::Rect(200, 100 / 1.414f), device_scale_factor, device_scale_factor); |
| expected_region2 = |
| gfx::ScaleToEnclosingRect(gfx::Rect(100 + 100 / 1.414f, 100 / 1.414f), |
| device_scale_factor, device_scale_factor); |
| } |
| |
| // Since iframe is clipped into an octagon, we expect to do slow path hit |
| // test on the iframe. |
| DCHECK(hit_test_data.size() >= 3); |
| if (!features::IsVizHitTestingSurfaceLayerEnabled()) { |
| EXPECT_TRUE(expected_region1.ApproximatelyEqual(hit_test_data[2].rect, |
| 1 + device_scale_factor)); |
| } else { |
| EXPECT_TRUE(expected_region1.ApproximatelyEqual(hit_test_data[2].rect, |
| 1 + device_scale_factor) || |
| expected_region2.ApproximatelyEqual(hit_test_data[2].rect, |
| 1 + device_scale_factor)); |
| } |
| EXPECT_TRUE( |
| expected_transform.ApproximatelyEqual(hit_test_data[2].transform())); |
| EXPECT_EQ(kSlowHitTestFlags, hit_test_data[2].flags); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestDataGenerationBrowserTest, |
| ClipPathOOPIF) { |
| if (!features::IsVizHitTestingEnabled()) |
| return; |
| auto hit_test_data = |
| SetupAndGetHitTestData("/frame_tree/page_with_clip_path_iframe.html"); |
| float device_scale_factor = current_device_scale_factor(); |
| gfx::Transform expected_transform; |
| gfx::Rect expected_region1 = gfx::ScaleToEnclosingRect( |
| gfx::Rect(100, 100), device_scale_factor, device_scale_factor); |
| gfx::Rect expected_region2 = gfx::ScaleToEnclosingRect( |
| gfx::Rect(80, 80), device_scale_factor, device_scale_factor); |
| |
| // Since iframe is clipped into an irregular quadrilateral, we expect to do |
| // slow path hit test on the iframe. |
| DCHECK(hit_test_data.size() >= 3); |
| // When BlinkGenPropertyTrees is enabled, the visible rect calculated for the |
| // OOPIF is different to that when BlinkGenPropertyTrees is disabled. So the |
| // test is considered passed if either of the regions equals to hit test |
| // region. |
| if (features::IsVizHitTestingSurfaceLayerEnabled()) { |
| EXPECT_TRUE(expected_region1.ApproximatelyEqual(hit_test_data[2].rect, |
| 1 + device_scale_factor) || |
| expected_region2.ApproximatelyEqual(hit_test_data[2].rect, |
| 1 + device_scale_factor)); |
| |
| } else { |
| EXPECT_TRUE(expected_region1.ApproximatelyEqual(hit_test_data[2].rect, |
| 1 + device_scale_factor)); |
| } |
| EXPECT_TRUE( |
| expected_transform.ApproximatelyEqual(hit_test_data[2].transform())); |
| EXPECT_EQ(kSlowHitTestFlags, hit_test_data[2].flags); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestDataGenerationBrowserTest, |
| OverlappedOOPIF) { |
| if (!features::IsVizHitTestingEnabled()) |
| return; |
| auto hit_test_data = |
| SetupAndGetHitTestData("/frame_tree/page_with_overlapped_iframes.html"); |
| float device_scale_factor = current_device_scale_factor(); |
| gfx::Transform expected_transform1; |
| gfx::Transform expected_transform2; |
| expected_transform2.matrix().postTranslate(-100 * device_scale_factor, 0, 0); |
| |
| gfx::Rect expected_region = gfx::ScaleToEnclosingRect( |
| gfx::Rect(100, 100), device_scale_factor, device_scale_factor); |
| |
| // Since iframe is occluded by a div in parent frame, we expect to do slow hit |
| // test. |
| DCHECK(hit_test_data.size() >= 4); |
| EXPECT_EQ(expected_region.ToString(), hit_test_data[3].rect.ToString()); |
| EXPECT_TRUE( |
| expected_transform1.ApproximatelyEqual(hit_test_data[3].transform())); |
| EXPECT_EQ(kSlowHitTestFlags, hit_test_data[3].flags); |
| EXPECT_EQ(expected_region.ToString(), hit_test_data[2].rect.ToString()); |
| EXPECT_TRUE( |
| expected_transform2.ApproximatelyEqual(hit_test_data[2].transform())); |
| if (features::IsVizHitTestingDrawQuadEnabled()) |
| EXPECT_EQ(kSlowHitTestFlags, hit_test_data[2].flags); |
| else if (features::IsVizHitTestingSurfaceLayerEnabled()) |
| EXPECT_EQ(kFastHitTestFlags, hit_test_data[2].flags); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestDataGenerationBrowserTest, |
| MaskedOOPIF) { |
| if (!features::IsVizHitTestingEnabled()) |
| return; |
| auto hit_test_data = |
| SetupAndGetHitTestData("/frame_tree/page_with_masked_iframe.html"); |
| float device_scale_factor = current_device_scale_factor(); |
| gfx::Transform expected_transform; |
| gfx::Rect expected_region = gfx::ScaleToEnclosingRect( |
| gfx::Rect(200, 200), device_scale_factor, device_scale_factor); |
| |
| // Since iframe clipped by clip-path and has a mask layer, we expect to do |
| // slow path hit testing. |
| DCHECK(hit_test_data.size() >= 3); |
| EXPECT_EQ(expected_region.ToString(), hit_test_data[2].rect.ToString()); |
| EXPECT_TRUE( |
| expected_transform.ApproximatelyEqual(hit_test_data[2].transform())); |
| EXPECT_EQ(kSlowHitTestFlags, hit_test_data[2].flags); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestDataGenerationBrowserTest, |
| AncestorMaskedOOPIF) { |
| if (!features::IsVizHitTestingEnabled()) |
| return; |
| auto hit_test_data = SetupAndGetHitTestData( |
| "/frame_tree/page_with_ancestor_masked_iframe.html"); |
| float device_scale_factor = current_device_scale_factor(); |
| gfx::Transform expected_transform; |
| gfx::Rect expected_region; |
| if (features::IsVizHitTestingSurfaceLayerEnabled()) { |
| expected_region = gfx::ScaleToEnclosingRect( |
| gfx::Rect(100, 100), device_scale_factor, device_scale_factor); |
| } else { |
| expected_region = gfx::ScaleToEnclosingRect( |
| gfx::Rect(200, 200), device_scale_factor, device_scale_factor); |
| } |
| |
| // Since iframe clipped by clip-path and has a mask layer, we expect to do |
| // slow path hit testing. |
| DCHECK(hit_test_data.size() >= 3); |
| EXPECT_EQ(expected_region.ToString(), hit_test_data[2].rect.ToString()); |
| EXPECT_TRUE( |
| expected_transform.ApproximatelyEqual(hit_test_data[2].transform())); |
| EXPECT_EQ(kSlowHitTestFlags, hit_test_data[2].flags); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestDataGenerationBrowserTest, |
| PointerEventsNoneOOPIF) { |
| if (!features::IsVizHitTestingEnabled()) |
| return; |
| auto hit_test_data = SetupAndGetHitTestData( |
| "/frame_tree/page_with_positioned_frame_pointer-events_none.html", 0); |
| float device_scale_factor = current_device_scale_factor(); |
| gfx::Transform expected_transform; |
| gfx::Rect expected_region = gfx::ScaleToEnclosingRect( |
| gfx::Rect(1, 1), device_scale_factor, device_scale_factor); |
| expected_transform.Translate(-2 * device_scale_factor, |
| -2 * device_scale_factor); |
| gfx::Rect expected_region2 = gfx::ScaleToEnclosingRect( |
| gfx::Rect(100, 100), device_scale_factor, device_scale_factor); |
| gfx::Transform expected_transform2; |
| expected_transform2.Translate(-52 * device_scale_factor, |
| -52 * device_scale_factor); |
| |
| // We should not submit hit test region for iframes with pointer-events: none |
| // in /2 hit-testing; we submit data in /1 but with Ignore flag set. |
| if (features::IsVizHitTestingSurfaceLayerEnabled()) { |
| DCHECK(hit_test_data.size() == 3); |
| EXPECT_EQ(expected_region.ToString(), hit_test_data[2].rect.ToString()); |
| EXPECT_TRUE( |
| expected_transform.ApproximatelyEqual(hit_test_data[2].transform())); |
| EXPECT_EQ(kFastHitTestFlags, hit_test_data[2].flags); |
| } else if (features::IsVizHitTestingDrawQuadEnabled()) { |
| DCHECK(hit_test_data.size() == 4); |
| EXPECT_EQ(expected_region2.ToString(), hit_test_data[3].rect.ToString()); |
| EXPECT_TRUE( |
| expected_transform2.ApproximatelyEqual(hit_test_data[3].transform())); |
| EXPECT_EQ(kSlowHitTestFlags | viz::HitTestRegionFlags::kHitTestIgnore, |
| hit_test_data[3].flags); |
| } |
| |
| // Check that an update on the css property can trigger an update in submitted |
| // hit test data. |
| EXPECT_TRUE(ExecuteScript(web_contents(), |
| "document.getElementsByTagName('iframe')[0].style." |
| "pointerEvents = 'auto';\n")); |
| |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetFrameTree() |
| ->root(); |
| |
| ASSERT_EQ(2U, root->child_count()); |
| RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>( |
| root->current_frame_host()->GetRenderWidgetHost()->GetView()); |
| |
| { |
| MainThreadFrameObserver observer( |
| root->current_frame_host()->GetRenderWidgetHost()); |
| observer.Wait(); |
| } |
| { |
| MainThreadFrameObserver observer( |
| root->child_at(0)->current_frame_host()->GetRenderWidgetHost()); |
| observer.Wait(); |
| } |
| { |
| MainThreadFrameObserver observer( |
| root->child_at(1)->current_frame_host()->GetRenderWidgetHost()); |
| observer.Wait(); |
| } |
| |
| WaitForHitTestDataOrChildSurfaceReady( |
| root->child_at(0)->current_frame_host()); |
| WaitForHitTestDataOrChildSurfaceReady( |
| root->child_at(1)->current_frame_host()); |
| |
| HitTestRegionObserver observer(rwhv_root->GetRootFrameSinkId()); |
| observer.WaitForHitTestData(); |
| hit_test_data = observer.GetHitTestData(); |
| MaybeStripHitTestData(&hit_test_data); |
| |
| DCHECK(hit_test_data.size() == 4); |
| EXPECT_EQ(expected_region.ToString(), hit_test_data[2].rect.ToString()); |
| EXPECT_TRUE( |
| expected_transform.ApproximatelyEqual(hit_test_data[2].transform())); |
| // Non v2 hit-testing should still treat OOPIFs as slow path. |
| if (features::IsVizHitTestingSurfaceLayerEnabled()) { |
| EXPECT_EQ(kFastHitTestFlags, hit_test_data[2].flags); |
| } else { |
| EXPECT_EQ(kSlowHitTestFlags, hit_test_data[2].flags); |
| } |
| |
| EXPECT_EQ(expected_region2.ToString(), hit_test_data[3].rect.ToString()); |
| EXPECT_TRUE( |
| expected_transform2.ApproximatelyEqual(hit_test_data[3].transform())); |
| if (features::IsVizHitTestingSurfaceLayerEnabled()) { |
| EXPECT_EQ(kFastHitTestFlags, hit_test_data[3].flags); |
| } else { |
| EXPECT_EQ(kSlowHitTestFlags, hit_test_data[3].flags); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestDataGenerationBrowserTest, |
| OccludedOOPIF) { |
| if (!features::IsVizHitTestingSurfaceLayerEnabled()) |
| return; |
| |
| auto hit_test_data = |
| SetupAndGetHitTestData("/frame_tree/page_with_occluded_iframes.html"); |
| float device_scale_factor = current_device_scale_factor(); |
| gfx::Transform expected_transform1; |
| gfx::Transform expected_transform2; |
| expected_transform2.Translate(-110 * device_scale_factor, 0); |
| |
| // We should not skip OOPIFs that are occluded by parent frame elements, since |
| // in cc an element's bound may not be its hit test area. |
| DCHECK(hit_test_data.size() == 4); |
| EXPECT_TRUE(ApproximatelyEqual( |
| TransformRectToQuadF(gfx::Rect(100, 100), expected_transform1), |
| TransformRectToQuadF(hit_test_data[3]))); |
| EXPECT_EQ(kSlowHitTestFlags, hit_test_data[3].flags); |
| |
| EXPECT_TRUE(ApproximatelyEqual( |
| TransformRectToQuadF(gfx::Rect(100, 100), expected_transform2), |
| TransformRectToQuadF(hit_test_data[2]))); |
| EXPECT_EQ(kSlowHitTestFlags, hit_test_data[2].flags); |
| } |
| |
| static const int kHitTestOption[] = {0, 1, 2}; |
| static const float kOneScale[] = {1.f}; |
| |
| INSTANTIATE_TEST_SUITE_P(/* no prefix */, |
| SitePerProcessHitTestBrowserTest, |
| testing::Combine(testing::ValuesIn(kHitTestOption), |
| testing::ValuesIn(kOneScale))); |
| // TODO(wjmaclean): Since the next two test fixtures only differ in DSF |
| // values, should we combine them into one using kMultiScale? This |
| // approach would make it more difficult to disable individual scales on |
| // particular platforms. |
| INSTANTIATE_TEST_SUITE_P(/* no prefix */, |
| SitePerProcessHighDPIHitTestBrowserTest, |
| testing::Combine(testing::ValuesIn(kHitTestOption), |
| testing::ValuesIn(kOneScale))); |
| INSTANTIATE_TEST_SUITE_P(/* no prefix */, |
| SitePerProcessNonIntegerScaleFactorHitTestBrowserTest, |
| testing::Combine(testing::ValuesIn(kHitTestOption), |
| testing::ValuesIn(kOneScale))); |
| INSTANTIATE_TEST_SUITE_P(/* no prefix */, |
| SitePerProcessEmulatedTouchBrowserTest, |
| testing::Combine(testing::ValuesIn(kHitTestOption), |
| testing::ValuesIn(kOneScale))); |
| |
| INSTANTIATE_TEST_SUITE_P(/* no prefix */, |
| SitePerProcessHitTestDataGenerationBrowserTest, |
| testing::Combine(testing::ValuesIn(kHitTestOption), |
| testing::ValuesIn(kOneScale))); |
| #if defined(USE_AURA) |
| static const float kMultiScale[] = {1.f, 1.5f, 2.f}; |
| |
| INSTANTIATE_TEST_SUITE_P(/* no prefix */, |
| SitePerProcessInternalsHitTestBrowserTest, |
| testing::Combine(testing::ValuesIn(kHitTestOption), |
| testing::ValuesIn(kMultiScale))); |
| INSTANTIATE_TEST_SUITE_P(/* no prefix */, |
| SitePerProcessMouseWheelHitTestBrowserTest, |
| testing::Combine(testing::ValuesIn(kHitTestOption), |
| testing::ValuesIn(kOneScale))); |
| INSTANTIATE_TEST_SUITE_P(/* no prefix */, |
| SitePerProcessGestureHitTestBrowserTest, |
| testing::Combine(testing::ValuesIn(kHitTestOption), |
| testing::ValuesIn(kOneScale))); |
| #endif |
| |
| } // namespace content |