blob: 84ceaef7964188e52704178edf7981de61c50f87 [file] [log] [blame]
// 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/display/display_switches.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/gesture_detection/gesture_configuration.h"
#include "ui/events/gesture_detection/gesture_provider_config_helper.h"
#include "ui/events/platform/platform_event_source.h"
#include "ui/gfx/geometry/quad_f.h"
#if defined(USE_AURA)
#include "content/browser/renderer_host/render_widget_host_view_aura.h"
#include "content/public/browser/overscroll_configuration.h"
#include "content/test/mock_overscroll_controller_delegate_aura.h"
#include "ui/aura/window_tree_host.h"
#include "ui/events/event_rewriter.h"
#endif
#if defined(OS_MACOSX)
#include "ui/base/test/scoped_preferred_scroller_style_mac.h"
#endif
#if defined(OS_ANDROID)
#include "content/browser/renderer_host/render_widget_host_view_android.h"
#include "content/test/mock_overscroll_refresh_handler_android.h"
#endif
namespace content {
namespace {
constexpr float kHitTestTolerance = 1.f;
class TestInputEventObserver : public RenderWidgetHost::InputEventObserver {
public:
explicit TestInputEventObserver(RenderWidgetHost* host) : host_(host) {
host_->AddInputEventObserver(this);
}
~TestInputEventObserver() override { host_->RemoveInputEventObserver(this); }
bool EventWasReceived() const { return !events_received_.empty(); }
void ResetEventsReceived() { events_received_.clear(); }
blink::WebInputEvent::Type EventType() const {
DCHECK(EventWasReceived());
return events_received_.front();
}
const std::vector<blink::WebInputEvent::Type>& events_received() {
return events_received_;
}
const blink::WebInputEvent& event() const { return *event_; }
void OnInputEvent(const blink::WebInputEvent& event) override {
events_received_.push_back(event.GetType());
event_ = ui::WebInputEventTraits::Clone(event);
}
const std::vector<InputEventAckSource>& events_acked() {
return events_acked_;
}
void OnInputEventAck(InputEventAckSource source,
InputEventAckState state,
const blink::WebInputEvent&) override {
events_acked_.push_back(source);
}
private:
RenderWidgetHost* host_;
std::vector<blink::WebInputEvent::Type> events_received_;
std::vector<InputEventAckSource> events_acked_;
ui::WebScopedInputEvent event_;
DISALLOW_COPY_AND_ASSIGN(TestInputEventObserver);
};
// |position_in_widget| is in the coord space of |rwhv|.
template <typename PointType>
void SetWebEventPositions(blink::WebPointerProperties* event,
const PointType& position_in_widget,
RenderWidgetHostViewBase* rwhv,
RenderWidgetHostViewBase* rwhv_root) {
event->SetPositionInWidget(gfx::PointF(position_in_widget));
const gfx::PointF position_in_root =
rwhv->TransformPointToRootCoordSpaceF(event->PositionInWidget());
const gfx::PointF point_in_screen =
position_in_root + rwhv_root->GetViewBounds().OffsetFromOrigin();
event->SetPositionInScreen(point_in_screen.x(), point_in_screen.y());
}
// For convenience when setting the position in the space of the root RWHV.
template <typename PointType>
void SetWebEventPositions(blink::WebPointerProperties* event,
const PointType& position_in_widget,
RenderWidgetHostViewBase* rwhv_root) {
DCHECK(!rwhv_root->IsRenderWidgetHostViewChildFrame());
SetWebEventPositions(event, position_in_widget, rwhv_root, rwhv_root);
}
#if defined(USE_AURA)
// |event->location()| is in the coord space of |rwhv|.
void UpdateEventRootLocation(ui::LocatedEvent* event,
RenderWidgetHostViewBase* rwhv,
RenderWidgetHostViewBase* rwhv_root) {
const gfx::Point position_in_root =
rwhv->TransformPointToRootCoordSpace(event->location());
gfx::Point root_location = position_in_root;
aura::Window::ConvertPointToTarget(
rwhv_root->GetNativeView(), rwhv_root->GetNativeView()->GetRootWindow(),
&root_location);
event->set_root_location(root_location);
}
// For convenience when setting the position in the space of the root RWHV.
void UpdateEventRootLocation(ui::LocatedEvent* event,
RenderWidgetHostViewBase* rwhv_root) {
DCHECK(!rwhv_root->IsRenderWidgetHostViewChildFrame());
UpdateEventRootLocation(event, rwhv_root, rwhv_root);
}
#endif // defined(USE_AURA)
void RouteMouseEventAndWaitUntilDispatch(
RenderWidgetHostInputEventRouter* router,
RenderWidgetHostViewBase* root_view,
RenderWidgetHostViewBase* expected_target,
blink::WebMouseEvent* event) {
InputEventAckWaiter waiter(expected_target->GetRenderWidgetHost(),
event->GetType());
router->RouteMouseEvent(root_view, event, ui::LatencyInfo());
waiter.Wait();
}
// Dispatch |event| to the specified view using browser process hit testing.
void DispatchMouseEventAndWaitUntilDispatch(
WebContentsImpl* web_contents,
blink::WebMouseEvent& event,
RenderWidgetHostViewBase* location_view,
const gfx::PointF& location,
RenderWidgetHostViewBase* expected_target,
const gfx::PointF& expected_location) {
auto* router = web_contents->GetInputEventRouter();
RenderWidgetHostMouseEventMonitor monitor(
expected_target->GetRenderWidgetHost());
gfx::PointF root_location =
location_view->TransformPointToRootCoordSpaceF(location);
FrameTreeNode* root = web_contents->GetFrameTree()->root();
auto* root_view = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
SetWebEventPositions(&event, root_location, root_view);
RouteMouseEventAndWaitUntilDispatch(router, root_view, expected_target,
&event);
EXPECT_TRUE(monitor.EventWasReceived());
EXPECT_NEAR(expected_location.x(), monitor.event().PositionInWidget().x,
kHitTestTolerance)
<< " & original location was " << location.x() << ", " << location.y()
<< " & root_location was " << root_location.x() << ", "
<< root_location.y();
EXPECT_NEAR(expected_location.y(), monitor.event().PositionInWidget().y,
kHitTestTolerance);
}
// Wrapper for the above method that creates a MouseDown to send.
void DispatchMouseEventAndWaitUntilDispatch(
WebContentsImpl* web_contents,
RenderWidgetHostViewBase* location_view,
const gfx::PointF& location,
RenderWidgetHostViewBase* expected_target,
const gfx::PointF& expected_location) {
blink::WebMouseEvent down_event(
blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
down_event.button = blink::WebPointerProperties::Button::kLeft;
down_event.click_count = 1;
DispatchMouseEventAndWaitUntilDispatch(web_contents, down_event,
location_view, location,
expected_target, expected_location);
}
// Helper function that performs a surface hittest.
void SurfaceHitTestTestHelper(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/page_with_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_child,
gfx::PointF(5, 5), rwhv_child,
gfx::PointF(5, 5));
DispatchMouseEventAndWaitUntilDispatch(
web_contents, rwhv_root, gfx::PointF(2, 2), rwhv_root, gfx::PointF(2, 2));
}
void OverlapSurfaceHitTestHelper(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/page_with_content_overlap_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
gfx::PointF parent_location = gfx::PointF(5, 5);
parent_location =
rwhv_child->TransformPointToRootCoordSpaceF(parent_location);
DispatchMouseEventAndWaitUntilDispatch(
web_contents, rwhv_child, gfx::PointF(5, 5), rwhv_root, parent_location);
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_child,
gfx::PointF(95, 95), rwhv_child,
gfx::PointF(95, 95));
}
void NonFlatTransformedSurfaceHitTestHelper(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/page_with_non_flat_transformed_frame.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_child,
gfx::PointF(5, 5), rwhv_child,
gfx::PointF(5, 5));
}
void PerspectiveTransformedSurfaceHitTestHelper(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/page_with_perspective_transformed_frame.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
RenderFrameSubmissionObserver render_frame_submission_observer(web_contents);
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
// (90, 75) hit tests into the child frame that is positioned at (50, 50).
// Without other transformations this should result in a translated point
// of (40, 25), but the 45 degree 3-dimensional rotation of the frame about
// a vertical axis skews it.
// We can't allow DispatchMouseEventAndWaitUntilDispatch to compute the
// coordinates in the root space unless browser conversions with
// perspective transforms are first fixed. See https://crbug.com/854257.
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root,
gfx::PointF(90, 75), rwhv_child,
gfx::PointF(33, 23));
}
// Helper function that performs a surface hittest in nested frame.
void NestedSurfaceHitTestTestHelper(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/page_with_positioned_nested_frames.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* parent_iframe_node = root->child_at(0);
GURL site_url(embedded_test_server->GetURL(
"a.com", "/frame_tree/page_with_positioned_frame.html"));
EXPECT_EQ(site_url, parent_iframe_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
parent_iframe_node->current_frame_host()->GetSiteInstance());
FrameTreeNode* nested_iframe_node = parent_iframe_node->child_at(0);
GURL nested_site_url(embedded_test_server->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(nested_site_url, nested_iframe_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
nested_iframe_node->current_frame_host()->GetSiteInstance());
EXPECT_NE(parent_iframe_node->current_frame_host()->GetSiteInstance(),
nested_iframe_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewBase* rwhv_nested =
static_cast<RenderWidgetHostViewBase*>(
nested_iframe_node->current_frame_host()
->GetRenderWidgetHost()
->GetView());
WaitForHitTestDataOrChildSurfaceReady(
nested_iframe_node->current_frame_host());
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_nested,
gfx::PointF(10, 10), rwhv_nested,
gfx::PointF(10, 10));
}
void HitTestLayerSquashing(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/oopif_hit_test_layer_squashing.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
gfx::Vector2dF child_offset = rwhv_child->GetViewBounds().origin() -
rwhv_root->GetViewBounds().origin();
// Send a mouse-down on #B. The main-frame should receive it.
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root,
gfx::PointF(195, 11), rwhv_root,
gfx::PointF(195, 11));
// Send another event just below. The child-frame should receive it.
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root,
gfx::PointF(195, 30), rwhv_child,
gfx::PointF(195, 30) - child_offset);
// Send a mouse-down on #C.
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root,
gfx::PointF(35, 195), rwhv_root,
gfx::PointF(35, 195));
// Send a mouse-down to the right of #C so that it goes to the child frame.
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root,
gfx::PointF(55, 195), rwhv_child,
gfx::PointF(55, 195) - child_offset);
// Send a mouse-down to the right-bottom edge of the iframe.
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root,
gfx::PointF(195, 235), rwhv_child,
gfx::PointF(195, 235) - child_offset);
}
void HitTestWatermark(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/oopif_hit_test_watermark.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
gfx::Vector2dF child_offset = rwhv_child->GetViewBounds().origin() -
rwhv_root->GetViewBounds().origin();
const gfx::PointF child_location(100, 120);
// Send a mouse-down at the center of the iframe. This should go to the
// main-frame (since there's a translucent div on top of it).
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_child,
child_location, rwhv_root,
child_location + child_offset);
// Set 'pointer-events: none' on the div.
EXPECT_TRUE(ExecuteScript(web_contents, "W.style.pointerEvents = 'none';"));
// TODO(sunxd): Re-enable this test when surface layer hit test is able to
// handle pointer-events none. See https://crbug.com/841358.
// Dispatch another event at the same location. It should reach the oopif this
// time.
if (!features::IsVizHitTestingSurfaceLayerEnabled()) {
DispatchMouseEventAndWaitUntilDispatch(
web_contents, rwhv_child, child_location, rwhv_child, child_location);
}
}
#if defined(USE_AURA)
void HitTestRootWindowTransform(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
// Apply transform to root window to test that we respect root window
// transform when transforming event location.
gfx::Transform transform;
transform.RotateAboutXAxis(180.f);
transform.Translate(0.f,
-shell->window()->GetHost()->window()->bounds().height());
shell->window()->GetHost()->SetRootTransform(transform);
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/page_with_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_child,
gfx::PointF(5, 5), rwhv_child,
gfx::PointF(5, 5));
DispatchMouseEventAndWaitUntilDispatch(
web_contents, rwhv_root, gfx::PointF(2, 2), rwhv_root, gfx::PointF(2, 2));
}
#endif // defined(USE_AURA)
#if defined(USE_AURA)
bool ConvertJSONToPoint(const std::string& str, gfx::PointF* point) {
std::unique_ptr<base::Value> value = base::JSONReader::ReadDeprecated(str);
if (!value)
return false;
base::DictionaryValue* root;
if (!value->GetAsDictionary(&root))
return false;
double x, y;
if (!root->GetDouble("x", &x))
return false;
if (!root->GetDouble("y", &y))
return false;
point->set_x(x);
point->set_y(y);
return true;
}
bool ConvertJSONToRect(const std::string& str, gfx::Rect* rect) {
std::unique_ptr<base::Value> value = base::JSONReader::ReadDeprecated(str);
if (!value)
return false;
base::DictionaryValue* root;
if (!value->GetAsDictionary(&root))
return false;
int x, y, width, height;
if (!root->GetInteger("x", &x))
return false;
if (!root->GetInteger("y", &y))
return false;
if (!root->GetInteger("width", &width))
return false;
if (!root->GetInteger("height", &height))
return false;
rect->set_x(x);
rect->set_y(y);
rect->set_width(width);
rect->set_height(height);
return true;
}
#endif // defined(USE_AURA)
// Class for intercepting SetMouseCapture messages being sent to a
// RenderWidgetHost. Note that this only works for RenderWidgetHosts that
// are attached to RenderFrameHosts, and not those for page popups, which
// use different bindings.
class SetMouseCaptureInterceptor
: public base::RefCountedThreadSafe<SetMouseCaptureInterceptor>,
public mojom::WidgetInputHandlerHostInterceptorForTesting {
public:
SetMouseCaptureInterceptor(RenderWidgetHostImpl* host)
: msg_received_(false),
capturing_(false),
host_(host),
impl_(binding().SwapImplForTesting(this)) {}
bool Capturing() const { return capturing_; }
void Wait() {
DCHECK(!run_loop_);
if (msg_received_) {
msg_received_ = false;
return;
}
run_loop_.reset(new base::RunLoop());
run_loop_->Run();
run_loop_.reset();
msg_received_ = false;
}
protected:
// mojom::WidgetInputHandlerHostInterceptorForTesting:
mojom::WidgetInputHandlerHost* GetForwardingInterface() override {
return impl_;
}
void SetMouseCapture(bool capturing) override {
capturing_ = capturing;
msg_received_ = true;
if (run_loop_)
run_loop_->Quit();
GetForwardingInterface()->SetMouseCapture(capturing);
}
private:
friend class base::RefCountedThreadSafe<SetMouseCaptureInterceptor>;
~SetMouseCaptureInterceptor() override {
binding().SwapImplForTesting(impl_);
}
mojo::Binding<mojom::WidgetInputHandlerHost>& binding() {
return static_cast<InputRouterImpl*>(host_->input_router())
->frame_host_binding_for_testing();
}
std::unique_ptr<base::RunLoop> run_loop_;
bool msg_received_;
bool capturing_;
RenderWidgetHostImpl* host_;
mojom::WidgetInputHandlerHost* impl_;
DISALLOW_COPY_AND_ASSIGN(SetMouseCaptureInterceptor);
};
#if defined(USE_AURA)
// A class to allow intercepting and discarding of all system-level events
// that might otherwise cause unpredictable behaviour in tests.
class SystemEventRewriter : public ui::EventRewriter {
public:
// Helper class to allow events to pass through for the lifetime of the
// object. Use this when tests generate events. This is needed under mash
// because the generate events reach SystemEventRewriter and will be dropped
// if there is no ScopedAllow instance.
// Note that allowing system events can cause flakiness in browser tests that
// don't expect them.
class ScopedAllow {
public:
explicit ScopedAllow(SystemEventRewriter* rewriter) : rewriter_(rewriter) {
++rewriter_->num_of_scoped_allows_;
}
~ScopedAllow() {
DCHECK_GT(rewriter_->num_of_scoped_allows_, 0);
--rewriter_->num_of_scoped_allows_;
}
private:
SystemEventRewriter* const rewriter_;
DISALLOW_COPY_AND_ASSIGN(ScopedAllow);
};
SystemEventRewriter() = default;
~SystemEventRewriter() override = default;
private:
ui::EventDispatchDetails RewriteEvent(
const ui::Event& event,
const Continuation continuation) override {
return num_of_scoped_allows_ ? SendEvent(continuation, &event)
: DiscardEvent(continuation);
}
// Count of ScopedAllow objects. When it is greater than 0, events are allowed
// to pass. Otherwise, they are discarded.
int num_of_scoped_allows_ = 0;
DISALLOW_COPY_AND_ASSIGN(SystemEventRewriter);
};
#endif
} // namespace
class SitePerProcessHitTestBrowserTest
: public testing::WithParamInterface<std::tuple<int, float>>,
public SitePerProcessBrowserTest {
public:
SitePerProcessHitTestBrowserTest() {}
#if defined(USE_AURA)
void PreRunTestOnMainThread() override {
SitePerProcessBrowserTest::PreRunTestOnMainThread();
// Disable system mouse events, which can interfere with tests.
shell()->window()->GetHost()->AddEventRewriter(&event_rewriter_);
}
void PostRunTestOnMainThread() override {
shell()->window()->GetHost()->RemoveEventRewriter(&event_rewriter_);
SitePerProcessBrowserTest::PostRunTestOnMainThread();
}
#endif
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
SitePerProcessBrowserTest::SetUpCommandLine(command_line);
ui::PlatformEventSource::SetIgnoreNativePlatformEvents(true);
const char kParam[] = "provider";
std::map<std::string, std::string> parameters;
if (std::get<0>(GetParam()) == 1) {
parameters[kParam] = "draw_quad";
feature_list_.InitAndEnableFeatureWithParameters(
features::kEnableVizHitTest, parameters);
} else if (std::get<0>(GetParam()) == 2) {
parameters[kParam] = "surface_layer";
feature_list_.InitAndEnableFeatureWithParameters(
features::kEnableVizHitTest, parameters);
} else {
feature_list_.InitWithFeatures(
{},
{features::kVizDisplayCompositor, features::kEnableVizHitTestDrawQuad,
features::kEnableVizHitTestSurfaceLayer});
}
}
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);
}
// TODO(https://crbug.com/961135): disabled because tests are flaky
IN_PROC_BROWSER_TEST_P(SitePerProcessInternalsHitTestBrowserTest,
DISABLED_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.
// https://crbug.com/959848: Flaky on Linux MSAN bots
#if defined(OS_LINUX)
#define MAYBE_TouchAndGestureEventPositionChange \
DISABLED_TouchAndGestureEventPositionChange
#else
#define MAYBE_TouchAndGestureEventPositionChange \
TouchAndGestureEventPositionChange
#endif
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
MAYBE_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::WebGestureDevice::kTouchscreen);
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::WebGestureDevice::kTouchscreen);
gesture_scroll_begin.unique_touch_event_id = 2;
gesture_scroll_begin.data.scroll_begin.delta_hint_units =
blink::WebScrollGranularity::kScrollByPrecisePixel;
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::WebGestureDevice::kTouchscreen);
gesture_scroll_update.unique_touch_event_id = 2;
gesture_scroll_update.data.scroll_update.delta_units =
blink::WebScrollGranularity::kScrollByPrecisePixel;
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::WebGestureDevice::kTouchscreen);
gesture_scroll_end.unique_touch_event_id = 3;
gesture_scroll_end.data.scroll_end.delta_units =
blink::WebScrollGranularity::kScrollByPrecisePixel;
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();
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::WebGestureDevice::kTouchscreen);
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::WebGestureDevice::kTouchscreen,
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::WebGestureDevice::kTouchscreen);
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::kStartTouchscreenThresholdDips;
#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::WebGestureDevice::kTouchscreen);
gesture_scroll_begin.unique_touch_event_id = 1;
gesture_scroll_begin.data.scroll_begin.delta_hint_units =
blink::WebScrollGranularity::kScrollByPrecisePixel;
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::WebGestureDevice::kTouchscreen);
gesture_scroll_update.unique_touch_event_id = 1;
gesture_scroll_update.data.scroll_update.delta_units =
blink::WebScrollGranularity::kScrollByPrecisePixel;
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::WebGestureDevice::kTouchscreen);
gesture_scroll_end.unique_touch_event_id = 1;
gesture_scroll_end.data.scroll_end.delta_units =
blink::WebScrollGranularity::kScrollByPrecisePixel;
mock_overscroll_observer->Reset();
router->RouteGestureEvent(rwhv_root, &gesture_scroll_end,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
mock_overscroll_observer->WaitForEnd();
// Ensure that the method of providing the child's scroll events to the root
// does not leave the child in an invalid state.
gesture_end_observer_child.Wait();
}
#endif // defined(USE_AURA) || defined(OS_ANDROID)
// Test that an ET_SCROLL event sent to an out-of-process iframe correctly
// results in a scroll. This is only handled by RenderWidgetHostViewAura
// and is needed for trackpad scrolling on Chromebooks.
#if defined(USE_AURA)
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, ScrollEventToOOPIF) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server()->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewAura* rwhv_parent =
static_cast<RenderWidgetHostViewAura*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
// Create listener for input events.
TestInputEventObserver child_frame_monitor(
child_node->current_frame_host()->GetRenderWidgetHost());
// Send a ui::ScrollEvent that will hit test to the child frame.
InputEventAckWaiter waiter(
child_node->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kMouseWheel);
ui::ScrollEvent scroll_event(ui::ET_SCROLL, gfx::Point(75, 75),
ui::EventTimeForNow(), ui::EF_NONE, 0,
10, // Offsets
0, 10, // Offset ordinals
2);
UpdateEventRootLocation(&scroll_event, rwhv_parent);
rwhv_parent->OnScrollEvent(&scroll_event);
waiter.Wait();
// Verify that this a mouse wheel event was sent to the child frame renderer.
EXPECT_TRUE(child_frame_monitor.EventWasReceived());
EXPECT_TRUE(base::ContainsValue(child_frame_monitor.events_received(),
blink::WebInputEvent::kMouseWheel));
}
// Tests that touching an OOPIF editable element correctly resizes the
// viewport and scrolls the element into view so that the element is not
// occluded by the on screen keyboard (https://crbug.com/927483)
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
ScrollOOPIFEditableElement) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/oopif_form_scroll_main.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
// Get the first iframe, which contains the editable element
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server()->GetURL(
"baz.com", "/frame_tree/page_with_editable_elements.html"));
ASSERT_EQ(site_url, child_node->current_url());
// Make sure the child frame is indeed a OOPIF
EXPECT_TRUE(child_node->current_frame_host()->IsCrossProcessSubframe());
// Set focus on an element in the OOPIF
EXPECT_TRUE(ExecJs(child_node->current_frame_host(),
"document.getElementById('oopif_element').focus()"));
MainThreadFrameObserver observer(
root->current_frame_host()->GetRenderWidgetHost());
observer.Wait();
// We reset the scroll to 0,0 because setting focus on element
// will bring it into view
ASSERT_TRUE(ExecJs(root->current_frame_host(),
base::StringPrintf("window.scrollTo(%d, %d);", 0, 0)));
content::RenderFrameSubmissionObserver root_frame_observer(root);
gfx::Vector2dF zero_offset;
root_frame_observer.WaitForScrollOffset(zero_offset);
RenderWidgetHostViewChildFrame* child_render_widget_host_view_child_frame =
static_cast<RenderWidgetHostViewChildFrame*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewAura* parent_render_widget_host_aura =
static_cast<RenderWidgetHostViewAura*>(
child_render_widget_host_view_child_frame->GetRootView());
gfx::Size original_viewport_size =
parent_render_widget_host_aura->GetVisibleViewportSize();
int htmlScrollHeight = EvalJs(root->current_frame_host(),
"document.documentElement.scrollHeight")
.ExtractInt();
const int inset_height = 200;
parent_render_widget_host_aura->SetLastPointerType(
ui::EventPointerType::POINTER_TYPE_TOUCH);
parent_render_widget_host_aura->FocusedNodeTouched(true);
parent_render_widget_host_aura->SetInsets(gfx::Insets(0, 0, inset_height, 0));
// After focus on editable element, we expect element to be scrolled
// into view. Verify that the scroll offset on the root document
// matches a value that would make the OOPIF element visible.
// When a page is scrolled to the bottom
// window.scrollY == htmlScrollHeight - viewportHeight;
// Verify that setting insets scrolled to the bottom to make
// editable element visible.
gfx::Vector2dF final_root_offset(
0, htmlScrollHeight - (original_viewport_size.height() - inset_height));
root_frame_observer.WaitForScrollOffset(final_root_offset);
EXPECT_EQ(EvalJs(root->current_frame_host(), "window.scrollY").ExtractInt(),
htmlScrollHeight - original_viewport_size.height());
// Viewport should be resized by keyboard height
EXPECT_NE(parent_render_widget_host_aura->GetVisibleViewportSize(),
original_viewport_size);
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
InputEventRouterWheelCoalesceTest) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server()->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewAura* rwhv_parent =
static_cast<RenderWidgetHostViewAura*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
RenderWidgetHostInputEventRouter* router =
web_contents()->GetInputEventRouter();
// Create listener for input events.
TestInputEventObserver child_frame_monitor(
child_node->current_frame_host()->GetRenderWidgetHost());
InputEventAckWaiter waiter(
child_node->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kMouseWheel);
// Send a mouse wheel event to child.
blink::WebMouseWheelEvent wheel_event(
blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
SetWebEventPositions(&wheel_event, gfx::Point(75, 75), rwhv_parent);
wheel_event.delta_x = 10;
wheel_event.delta_y = 20;
wheel_event.phase = blink::WebMouseWheelEvent::kPhaseBegan;
router->RouteMouseWheelEvent(rwhv_parent, &wheel_event, ui::LatencyInfo());
// Send more mouse wheel events to the child. Since we are waiting for the
// async targeting on the first event, these new mouse wheel events should
// be coalesced properly.
blink::WebMouseWheelEvent wheel_event1(
blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
SetWebEventPositions(&wheel_event1, gfx::Point(70, 70), rwhv_parent);
wheel_event1.delta_x = 12;
wheel_event1.delta_y = 22;
wheel_event1.phase = blink::WebMouseWheelEvent::kPhaseChanged;
router->RouteMouseWheelEvent(rwhv_parent, &wheel_event1, ui::LatencyInfo());
blink::WebMouseWheelEvent wheel_event2(
blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
SetWebEventPositions(&wheel_event2, gfx::Point(65, 65), rwhv_parent);
wheel_event2.delta_x = 14;
wheel_event2.delta_y = 24;
wheel_event2.phase = blink::WebMouseWheelEvent::kPhaseChanged;
router->RouteMouseWheelEvent(rwhv_parent, &wheel_event2, ui::LatencyInfo());
// Since we are targeting child, event dispatch should not happen
// synchronously. Validate that the expected target does not receive the
// event immediately.
// When V2 surface layer hit testing is enabled, we expect to do synchronous
// event targeting on a child under some circumstances, so we expect the event
// immediately dispatched to the child.
if (!features::IsVizHitTestingSurfaceLayerEnabled())
EXPECT_FALSE(child_frame_monitor.EventWasReceived());
waiter.Wait();
EXPECT_TRUE(child_frame_monitor.EventWasReceived());
EXPECT_EQ(child_frame_monitor.EventType(), blink::WebInputEvent::kMouseWheel);
// Check if the two mouse-wheel update events are coalesced correctly.
const auto& gesture_event =
static_cast<const blink::WebGestureEvent&>(child_frame_monitor.event());
EXPECT_EQ(26 /* wheel_event1.delta_x + wheel_event2.delta_x */,
gesture_event.data.scroll_update.delta_x);
EXPECT_EQ(46 /* wheel_event1.delta_y + wheel_event2.delta_y */,
gesture_event.data.scroll_update.delta_y);
}
#endif // defined(USE_AURA)
// Test that mouse events are being routed to the correct RenderWidgetHostView
// based on coordinates.
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, SurfaceHitTestTest) {
SurfaceHitTestTestHelper(shell(), embedded_test_server());
}
// Same test as above, but runs in high-dpi mode.
// NOTE: This has to be renamed from SurfaceHitTestTest to
// HighDPISurfaceHitTestTest. Otherwise MAYBE_SurfaceHitTestTest gets #defined
// twice.
#if defined(OS_ANDROID) || defined(OS_WIN)
// High DPI browser tests are not needed on Android, and confuse some of the
// coordinate calculations. Android uses fixed device scale factor.
// Windows is disabled because of https://crbug.com/545547.
#define MAYBE_HighDPISurfaceHitTestTest DISABLED_HighDPISurfaceHitTestTest
#else
#define MAYBE_HighDPISurfaceHitTestTest HighDPISurfaceHitTestTest
#endif
IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest,
MAYBE_HighDPISurfaceHitTestTest) {
SurfaceHitTestTestHelper(shell(), embedded_test_server());
}
// TODO(https://crbug.com/948372): tests are flaky on ChromeOS and often
// timeout.
#if defined(OS_CHROMEOS)
#define MAYBE_NestedSurfaceHitTestTest DISABLED_NestedSurfaceHitTestTest
#else
#define MAYBE_NestedSurfaceHitTestTest NestedSurfaceHitTestTest
#endif
// 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,
MAYBE_NestedSurfaceHitTestTest) {
NestedSurfaceHitTestTestHelper(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest,
MAYBE_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) {