blob: 64ddda326298bdd9e1107d790d78e310e802c942 [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/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 "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/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/view_messages.h"
#include "content/common/widget_messages.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/screen_info.h"
#include "content/public/common/use_zoom_for_dsf_policy.h"
#include "content/public/common/web_preferences.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/hit_test_region_observer.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "content/shell/common/shell_switches.h"
#include "content/test/mock_overscroll_observer.h"
#include "ui/base/ui_base_features.h"
#include "ui/display/display_switches.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/gesture_detection/gesture_configuration.h"
#include "ui/events/gesture_detection/gesture_provider_config_helper.h"
#include "ui/gfx/geometry/quad_f.h"
#if defined(USE_AURA)
#include "content/browser/renderer_host/render_widget_host_view_aura.h"
#include "content/public/browser/overscroll_configuration.h"
#include "content/test/mock_overscroll_controller_delegate_aura.h"
#include "ui/aura/window_tree_host.h"
#include "ui/events/event_rewriter.h"
#endif
#if defined(OS_MACOSX)
#include "ui/base/test/scoped_preferred_scroller_style_mac.h"
#endif
#if defined(OS_ANDROID)
#include "content/browser/renderer_host/render_widget_host_view_android.h"
#include "content/test/mock_overscroll_refresh_handler_android.h"
#endif
namespace content {
namespace {
constexpr float kHitTestTolerance = 1.f;
class RenderWidgetHostMouseEventMonitor {
public:
explicit RenderWidgetHostMouseEventMonitor(RenderWidgetHost* host)
: host_(host), event_received_(false) {
mouse_callback_ =
base::Bind(&RenderWidgetHostMouseEventMonitor::MouseEventCallback,
base::Unretained(this));
host_->AddMouseEventCallback(mouse_callback_);
}
~RenderWidgetHostMouseEventMonitor() {
host_->RemoveMouseEventCallback(mouse_callback_);
}
bool EventWasReceived() const { return event_received_; }
void ResetEventReceived() { event_received_ = false; }
const blink::WebMouseEvent& event() const { return event_; }
private:
bool MouseEventCallback(const blink::WebMouseEvent& event) {
event_received_ = true;
event_ = event;
return false;
}
RenderWidgetHost::MouseEventCallback mouse_callback_;
RenderWidgetHost* host_;
bool event_received_;
blink::WebMouseEvent event_;
DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostMouseEventMonitor);
};
class TestInputEventObserver : public RenderWidgetHost::InputEventObserver {
public:
explicit TestInputEventObserver(RenderWidgetHost* host) : host_(host) {
host_->AddInputEventObserver(this);
}
~TestInputEventObserver() override { host_->RemoveInputEventObserver(this); }
bool EventWasReceived() const { return !events_received_.empty(); }
void ResetEventsReceived() { events_received_.clear(); }
blink::WebInputEvent::Type EventType() const {
DCHECK(EventWasReceived());
return events_received_.front();
}
const std::vector<blink::WebInputEvent::Type>& events_received() {
return events_received_;
}
const blink::WebInputEvent& event() const { return *event_; }
void OnInputEvent(const blink::WebInputEvent& event) override {
events_received_.push_back(event.GetType());
event_ = ui::WebInputEventTraits::Clone(event);
};
const std::vector<InputEventAckSource>& events_acked() {
return events_acked_;
}
void OnInputEventAck(InputEventAckSource source,
InputEventAckState state,
const blink::WebInputEvent&) override {
events_acked_.push_back(source);
}
private:
RenderWidgetHost* host_;
std::vector<blink::WebInputEvent::Type> events_received_;
std::vector<InputEventAckSource> events_acked_;
ui::WebScopedInputEvent event_;
DISALLOW_COPY_AND_ASSIGN(TestInputEventObserver);
};
// |position_in_widget| is in the coord space of |rwhv|.
template <typename PointType>
void SetWebEventPositions(blink::WebPointerProperties* event,
const PointType& position_in_widget,
RenderWidgetHostViewBase* rwhv,
RenderWidgetHostViewBase* rwhv_root) {
event->SetPositionInWidget(gfx::PointF(position_in_widget));
const gfx::PointF position_in_root =
rwhv->TransformPointToRootCoordSpaceF(event->PositionInWidget());
const gfx::PointF point_in_screen =
position_in_root + rwhv_root->GetViewBounds().OffsetFromOrigin();
event->SetPositionInScreen(point_in_screen.x(), point_in_screen.y());
}
// For convenience when setting the position in the space of the root RWHV.
template <typename PointType>
void SetWebEventPositions(blink::WebPointerProperties* event,
const PointType& position_in_widget,
RenderWidgetHostViewBase* rwhv_root) {
DCHECK(!rwhv_root->IsRenderWidgetHostViewChildFrame());
SetWebEventPositions(event, position_in_widget, rwhv_root, rwhv_root);
}
#if defined(USE_AURA)
// |event->location()| is in the coord space of |rwhv|.
void UpdateEventRootLocation(ui::LocatedEvent* event,
RenderWidgetHostViewBase* rwhv,
RenderWidgetHostViewBase* rwhv_root) {
const gfx::Point position_in_root =
rwhv->TransformPointToRootCoordSpace(event->location());
gfx::Point root_location = position_in_root;
aura::Window::ConvertPointToTarget(
rwhv_root->GetNativeView(), rwhv_root->GetNativeView()->GetRootWindow(),
&root_location);
event->set_root_location(root_location);
}
// For convenience when setting the position in the space of the root RWHV.
void UpdateEventRootLocation(ui::LocatedEvent* event,
RenderWidgetHostViewBase* rwhv_root) {
DCHECK(!rwhv_root->IsRenderWidgetHostViewChildFrame());
UpdateEventRootLocation(event, rwhv_root, rwhv_root);
}
#endif // defined(USE_AURA)
void RouteMouseEventAndWaitUntilDispatch(
RenderWidgetHostInputEventRouter* router,
RenderWidgetHostViewBase* root_view,
RenderWidgetHostViewBase* expected_target,
blink::WebMouseEvent* event) {
InputEventAckWaiter waiter(expected_target->GetRenderWidgetHost(),
event->GetType());
router->RouteMouseEvent(root_view, event, ui::LatencyInfo());
waiter.Wait();
}
// Dispatch |event| to the specified view using browser process hit testing.
void DispatchMouseEventAndWaitUntilDispatch(
WebContentsImpl* web_contents,
blink::WebMouseEvent& event,
RenderWidgetHostViewBase* location_view,
const gfx::PointF& location,
RenderWidgetHostViewBase* expected_target,
const gfx::PointF& expected_location) {
auto* router = web_contents->GetInputEventRouter();
RenderWidgetHostMouseEventMonitor monitor(
expected_target->GetRenderWidgetHost());
gfx::PointF root_location =
location_view->TransformPointToRootCoordSpaceF(location);
FrameTreeNode* root = web_contents->GetFrameTree()->root();
auto* root_view = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
SetWebEventPositions(&event, root_location, root_view);
RouteMouseEventAndWaitUntilDispatch(router, root_view, expected_target,
&event);
EXPECT_TRUE(monitor.EventWasReceived());
EXPECT_NEAR(expected_location.x(), monitor.event().PositionInWidget().x,
kHitTestTolerance)
<< " & original location was " << location.x() << ", " << location.y()
<< " & root_location was " << root_location.x() << ", "
<< root_location.y();
EXPECT_NEAR(expected_location.y(), monitor.event().PositionInWidget().y,
kHitTestTolerance);
}
// Wrapper for the above method that creates a MouseDown to send.
void DispatchMouseEventAndWaitUntilDispatch(
WebContentsImpl* web_contents,
RenderWidgetHostViewBase* location_view,
const gfx::PointF& location,
RenderWidgetHostViewBase* expected_target,
const gfx::PointF& expected_location) {
blink::WebMouseEvent down_event(
blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
down_event.button = blink::WebPointerProperties::Button::kLeft;
down_event.click_count = 1;
DispatchMouseEventAndWaitUntilDispatch(web_contents, down_event,
location_view, location,
expected_target, expected_location);
}
// Helper function that performs a surface hittest.
void SurfaceHitTestTestHelper(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/page_with_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_child,
gfx::PointF(5, 5), rwhv_child,
gfx::PointF(5, 5));
DispatchMouseEventAndWaitUntilDispatch(
web_contents, rwhv_root, gfx::PointF(2, 2), rwhv_root, gfx::PointF(2, 2));
}
void OverlapSurfaceHitTestHelper(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/page_with_content_overlap_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
gfx::PointF parent_location = gfx::PointF(5, 5);
parent_location =
rwhv_child->TransformPointToRootCoordSpaceF(parent_location);
DispatchMouseEventAndWaitUntilDispatch(
web_contents, rwhv_child, gfx::PointF(5, 5), rwhv_root, parent_location);
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_child,
gfx::PointF(95, 95), rwhv_child,
gfx::PointF(95, 95));
}
void NonFlatTransformedSurfaceHitTestHelper(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/page_with_non_flat_transformed_frame.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_child,
gfx::PointF(5, 5), rwhv_child,
gfx::PointF(5, 5));
}
void PerspectiveTransformedSurfaceHitTestHelper(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/page_with_perspective_transformed_frame.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
RenderFrameSubmissionObserver render_frame_submission_observer(web_contents);
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
// (90, 75) hit tests into the child frame that is positioned at (50, 50).
// Without other transformations this should result in a translated point
// of (40, 25), but the 45 degree 3-dimensional rotation of the frame about
// a vertical axis skews it.
// We can't allow DispatchMouseEventAndWaitUntilDispatch to compute the
// coordinates in the root space unless browser conversions with
// perspective transforms are first fixed. See https://crbug.com/854257.
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root,
gfx::PointF(90, 75), rwhv_child,
gfx::PointF(33, 23));
}
// Helper function that performs a surface hittest in nested frame.
void NestedSurfaceHitTestTestHelper(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/page_with_positioned_nested_frames.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* parent_iframe_node = root->child_at(0);
GURL site_url(embedded_test_server->GetURL(
"a.com", "/frame_tree/page_with_positioned_frame.html"));
EXPECT_EQ(site_url, parent_iframe_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
parent_iframe_node->current_frame_host()->GetSiteInstance());
FrameTreeNode* nested_iframe_node = parent_iframe_node->child_at(0);
GURL nested_site_url(embedded_test_server->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(nested_site_url, nested_iframe_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
nested_iframe_node->current_frame_host()->GetSiteInstance());
EXPECT_NE(parent_iframe_node->current_frame_host()->GetSiteInstance(),
nested_iframe_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewBase* rwhv_nested =
static_cast<RenderWidgetHostViewBase*>(
nested_iframe_node->current_frame_host()
->GetRenderWidgetHost()
->GetView());
WaitForHitTestDataOrChildSurfaceReady(
nested_iframe_node->current_frame_host());
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_nested,
gfx::PointF(10, 10), rwhv_nested,
gfx::PointF(10, 10));
}
void HitTestLayerSquashing(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/oopif_hit_test_layer_squashing.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
gfx::Vector2dF child_offset = rwhv_child->GetViewBounds().origin() -
rwhv_root->GetViewBounds().origin();
// Send a mouse-down on #B. The main-frame should receive it.
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root,
gfx::PointF(195, 11), rwhv_root,
gfx::PointF(195, 11));
// Send another event just below. The child-frame should receive it.
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root,
gfx::PointF(195, 30), rwhv_child,
gfx::PointF(195, 30) - child_offset);
// Send a mouse-down on #C.
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root,
gfx::PointF(35, 195), rwhv_root,
gfx::PointF(35, 195));
// Send a mouse-down to the right of #C so that it goes to the child frame.
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root,
gfx::PointF(55, 195), rwhv_child,
gfx::PointF(55, 195) - child_offset);
// Send a mouse-down to the right-bottom edge of the iframe.
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root,
gfx::PointF(195, 235), rwhv_child,
gfx::PointF(195, 235) - child_offset);
}
void HitTestWatermark(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/oopif_hit_test_watermark.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
gfx::Vector2dF child_offset = rwhv_child->GetViewBounds().origin() -
rwhv_root->GetViewBounds().origin();
const gfx::PointF child_location(100, 120);
// Send a mouse-down at the center of the iframe. This should go to the
// main-frame (since there's a translucent div on top of it).
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_child,
child_location, rwhv_root,
child_location + child_offset);
// Set 'pointer-events: none' on the div.
EXPECT_TRUE(ExecuteScript(web_contents, "W.style.pointerEvents = 'none';"));
// TODO(sunxd): Re-enable this test when surface layer hit test is able to
// handle pointer-events none. See https://crbug.com/841358.
// Dispatch another event at the same location. It should reach the oopif this
// time.
if (!features::IsVizHitTestingSurfaceLayerEnabled()) {
DispatchMouseEventAndWaitUntilDispatch(
web_contents, rwhv_child, child_location, rwhv_child, child_location);
}
}
#if defined(USE_AURA)
void HitTestRootWindowTransform(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
// Apply transform to root window to test that we respect root window
// transform when transforming event location.
gfx::Transform transform;
transform.RotateAboutXAxis(180.f);
transform.Translate(0.f,
-shell->window()->GetHost()->window()->bounds().height());
shell->window()->GetHost()->SetRootTransform(transform);
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/page_with_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_child,
gfx::PointF(5, 5), rwhv_child,
gfx::PointF(5, 5));
DispatchMouseEventAndWaitUntilDispatch(
web_contents, rwhv_root, gfx::PointF(2, 2), rwhv_root, gfx::PointF(2, 2));
}
#endif // defined(USE_AURA)
#if defined(USE_AURA)
bool ConvertJSONToPoint(const std::string& str, gfx::PointF* point) {
std::unique_ptr<base::Value> value = base::JSONReader::Read(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::Read(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::EventRewriteStatus RewriteEvent(
const ui::Event& event,
std::unique_ptr<ui::Event>* new_event) override {
return num_of_scoped_allows_ ? ui::EVENT_REWRITE_CONTINUE
: ui::EVENT_REWRITE_DISCARD;
}
ui::EventRewriteStatus NextDispatchEvent(
const ui::Event& event,
std::unique_ptr<ui::Event>* new_event) override {
NOTREACHED();
return ui::EVENT_REWRITE_CONTINUE;
}
// 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);
if (std::get<0>(GetParam()) == 1) {
feature_list_.InitAndEnableFeature(features::kEnableVizHitTestDrawQuad);
} else if (std::get<0>(GetParam()) == 2) {
feature_list_.InitWithFeatures({features::kEnableVizHitTestSurfaceLayer},
{features::kEnableVizHitTestDrawQuad});
} else {
feature_list_.InitWithFeatures({}, {features::kEnableVizHitTestDrawQuad,
features::kVizDisplayCompositor});
}
}
base::test::ScopedFeatureList feature_list_;
#if defined(USE_AURA)
SystemEventRewriter event_rewriter_;
#endif
};
//
// SitePerProcessHighDPIHitTestBrowserTest
//
class SitePerProcessHighDPIHitTestBrowserTest
: public SitePerProcessHitTestBrowserTest {
public:
const double kDeviceScaleFactor = 2.0;
SitePerProcessHighDPIHitTestBrowserTest() {}
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
SitePerProcessHitTestBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII(
switches::kForceDeviceScaleFactor,
base::StringPrintf("%f", kDeviceScaleFactor));
}
};
//
// SitePerProcessNonIntegerScaleFactorHitTestBrowserTest
//
class SitePerProcessNonIntegerScaleFactorHitTestBrowserTest
: public SitePerProcessHitTestBrowserTest {
public:
const double kDeviceScaleFactor = 1.5;
SitePerProcessNonIntegerScaleFactorHitTestBrowserTest() {}
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
SitePerProcessHitTestBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII(
switches::kForceDeviceScaleFactor,
base::StringPrintf("%f", kDeviceScaleFactor));
}
};
// Restrict to Aura to we can use routable MouseWheel event via
// RenderWidgetHostViewAura::OnScrollEvent().
#if defined(USE_AURA)
class SitePerProcessInternalsHitTestBrowserTest
: public SitePerProcessHitTestBrowserTest {
public:
SitePerProcessInternalsHitTestBrowserTest() {}
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
SitePerProcessHitTestBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(switches::kExposeInternalsForTesting);
// Needed to guarantee the scrollable div we're testing with is not given
// its own compositing layer.
command_line->AppendSwitch(switches::kDisablePreferCompositingToLCDText);
command_line->AppendSwitchASCII(
switches::kForceDeviceScaleFactor,
base::StringPrintf("%f", std::get<1>(GetParam())));
}
};
IN_PROC_BROWSER_TEST_P(SitePerProcessInternalsHitTestBrowserTest,
ScrollNestedLocalNonFastScrollableDiv) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* parent_iframe_node = root->child_at(0);
GURL site_url(embedded_test_server()->GetURL(
"b.com", "/tall_page_with_local_iframe.html"));
NavigateFrameToURL(parent_iframe_node, site_url);
FrameTreeNode* nested_iframe_node = parent_iframe_node->child_at(0);
WaitForHitTestDataOrChildSurfaceReady(
nested_iframe_node->current_frame_host());
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
" +--Site B -- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://b.com/",
DepictFrameTree(root));
const char* get_element_location_script_fmt =
"var rect = "
"document.getElementById('%s').getBoundingClientRect();\n"
"var point = {\n"
" x: rect.left,\n"
" y: rect.top\n"
"};\n"
"window.domAutomationController.send(JSON.stringify(point));";
// Since the nested local b-frame shares the RenderWidgetHostViewChildFrame
// with the parent frame, we need to query element offsets in both documents
// before converting to root space coordinates for the wheel event.
std::string str;
EXPECT_TRUE(ExecuteScriptAndExtractString(
nested_iframe_node->current_frame_host(),
base::StringPrintf(get_element_location_script_fmt, "scrollable_div"),
&str));
gfx::PointF nested_point_f;
ConvertJSONToPoint(str, &nested_point_f);
EXPECT_TRUE(ExecuteScriptAndExtractString(
parent_iframe_node->current_frame_host(),
base::StringPrintf(get_element_location_script_fmt, "nested_frame"),
&str));
gfx::PointF parent_offset_f;
ConvertJSONToPoint(str, &parent_offset_f);
// Compute location for wheel event.
gfx::PointF point_f(parent_offset_f.x() + nested_point_f.x() + 5.f,
parent_offset_f.y() + nested_point_f.y() + 5.f);
RenderWidgetHostViewChildFrame* rwhv_nested =
static_cast<RenderWidgetHostViewChildFrame*>(
nested_iframe_node->current_frame_host()
->GetRenderWidgetHost()
->GetView());
point_f = rwhv_nested->TransformPointToRootCoordSpaceF(point_f);
RenderWidgetHostViewAura* rwhv_root = static_cast<RenderWidgetHostViewAura*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
gfx::PointF nested_in_parent;
rwhv_root->TransformPointToCoordSpaceForView(
point_f,
parent_iframe_node->current_frame_host()
->GetRenderWidgetHost()
->GetView(),
&nested_in_parent, viz::EventSource::MOUSE);
// Get original scroll position.
double div_scroll_top_start;
EXPECT_TRUE(ExecuteScriptAndExtractDouble(
nested_iframe_node->current_frame_host(),
"window.domAutomationController.send("
"document.getElementById('scrollable_div').scrollTop);",
&div_scroll_top_start));
EXPECT_EQ(0.0, div_scroll_top_start);
// Wait until renderer's compositor thread is synced. Otherwise the non fast
// scrollable regions won't be set when the event arrives.
MainThreadFrameObserver observer(rwhv_nested->GetRenderWidgetHost());
observer.Wait();
// Send a wheel to scroll the div.
gfx::Point location(point_f.x(), point_f.y());
ui::ScrollEvent scroll_event(ui::ET_SCROLL, location, ui::EventTimeForNow(),
0, 0, -ui::MouseWheelEvent::kWheelDelta, 0,
ui::MouseWheelEvent::kWheelDelta,
2); // This must be '2' or it gets silently
// dropped.
UpdateEventRootLocation(&scroll_event, rwhv_root);
InputEventAckWaiter ack_observer(
parent_iframe_node->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kGestureScrollUpdate);
rwhv_root->OnScrollEvent(&scroll_event);
ack_observer.Wait();
// Check compositor layers.
EXPECT_TRUE(ExecuteScriptAndExtractString(
nested_iframe_node->current_frame_host(),
"window.domAutomationController.send("
"window.internals.layerTreeAsText(document));",
&str));
// We expect the nested OOPIF to not have any compositor layers.
EXPECT_EQ(std::string(), str);
// Verify the div scrolled.
double div_scroll_top = div_scroll_top_start;
EXPECT_TRUE(ExecuteScriptAndExtractDouble(
nested_iframe_node->current_frame_host(),
"window.domAutomationController.send("
"document.getElementById('scrollable_div').scrollTop);",
&div_scroll_top));
EXPECT_NE(div_scroll_top_start, div_scroll_top);
}
IN_PROC_BROWSER_TEST_P(SitePerProcessInternalsHitTestBrowserTest,
NestedLocalNonFastScrollableDivCoordsAreLocal) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* parent_iframe_node = root->child_at(0);
GURL site_url(embedded_test_server()->GetURL(
"b.com", "/tall_page_with_local_iframe.html"));
NavigateFrameToURL(parent_iframe_node, site_url);
FrameTreeNode* nested_iframe_node = parent_iframe_node->child_at(0);
WaitForHitTestDataOrChildSurfaceReady(
nested_iframe_node->current_frame_host());
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
" +--Site B -- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://b.com/",
DepictFrameTree(root));
const char* get_element_location_script_fmt =
"var rect = "
"document.getElementById('%s').getBoundingClientRect();\n"
"var point = {\n"
" x: rect.left,\n"
" y: rect.top\n"
"};\n"
"window.domAutomationController.send(JSON.stringify(point));";
// Since the nested local b-frame shares the RenderWidgetHostViewChildFrame
// with the parent frame, we need to query element offsets in both documents
// before converting to root space coordinates for the wheel event.
std::string str;
EXPECT_TRUE(ExecuteScriptAndExtractString(
nested_iframe_node->current_frame_host(),
base::StringPrintf(get_element_location_script_fmt, "scrollable_div"),
&str));
gfx::PointF nested_point_f;
ConvertJSONToPoint(str, &nested_point_f);
int num_non_fast_region_rects;
EXPECT_TRUE(ExecuteScriptAndExtractInt(
parent_iframe_node->current_frame_host(),
"window.internals.markGestureScrollRegionDirty(document);\n"
"window.internals.forceCompositingUpdate(document);\n"
"var rects = window.internals.nonFastScrollableRects(document);\n"
"window.domAutomationController.send(rects.length);",
&num_non_fast_region_rects));
EXPECT_EQ(1, num_non_fast_region_rects);
EXPECT_TRUE(ExecuteScriptAndExtractString(
parent_iframe_node->current_frame_host(),
"var rect = {\n"
" x: rects[0].left,\n"
" y: rects[0].top,\n"
" width: rects[0].width,\n"
" height: rects[0].height\n"
"};\n"
"window.domAutomationController.send(JSON.stringify(rect));",
&str));
gfx::Rect non_fast_scrollable_rect_before_scroll;
ConvertJSONToRect(str, &non_fast_scrollable_rect_before_scroll);
EXPECT_TRUE(ExecuteScriptAndExtractString(
parent_iframe_node->current_frame_host(),
base::StringPrintf(get_element_location_script_fmt, "nested_frame"),
&str));
gfx::PointF parent_offset_f;
ConvertJSONToPoint(str, &parent_offset_f);
// Compute location for wheel event to scroll the parent with respect to the
// mainframe.
gfx::PointF point_f(parent_offset_f.x() + 1.f, parent_offset_f.y() + 1.f);
RenderWidgetHostViewChildFrame* rwhv_parent =
static_cast<RenderWidgetHostViewChildFrame*>(
parent_iframe_node->current_frame_host()
->GetRenderWidgetHost()
->GetView());
point_f = rwhv_parent->TransformPointToRootCoordSpaceF(point_f);
RenderWidgetHostViewAura* rwhv_root = static_cast<RenderWidgetHostViewAura*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
gfx::PointF nested_in_parent;
rwhv_root->TransformPointToCoordSpaceForView(
point_f,
parent_iframe_node->current_frame_host()
->GetRenderWidgetHost()
->GetView(),
&nested_in_parent);
// Get original scroll position.
double div_scroll_top_start;
EXPECT_TRUE(
ExecuteScriptAndExtractDouble(parent_iframe_node->current_frame_host(),
"window.domAutomationController.send("
"document.body.scrollTop);",
&div_scroll_top_start));
EXPECT_EQ(0.0, div_scroll_top_start);
// Send a wheel to scroll the parent containing the div.
gfx::Point location(point_f.x(), point_f.y());
ui::ScrollEvent scroll_event(ui::ET_SCROLL, location, ui::EventTimeForNow(),
0, 0, -ui::MouseWheelEvent::kWheelDelta, 0,
ui::MouseWheelEvent::kWheelDelta,
2); // This must be '2' or it gets silently
// dropped.
UpdateEventRootLocation(&scroll_event, rwhv_root);
InputEventAckWaiter ack_observer(
parent_iframe_node->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kGestureScrollUpdate);
rwhv_root->OnScrollEvent(&scroll_event);
ack_observer.Wait();
MainThreadFrameObserver thread_observer(rwhv_parent->GetRenderWidgetHost());
thread_observer.Wait();
// Check compositor layers.
EXPECT_TRUE(ExecuteScriptAndExtractString(
nested_iframe_node->current_frame_host(),
"window.domAutomationController.send("
"window.internals.layerTreeAsText(document));",
&str));
// We expect the nested OOPIF to not have any compositor layers.
EXPECT_EQ(std::string(), str);
// Verify the div scrolled.
double div_scroll_top = div_scroll_top_start;
EXPECT_TRUE(
ExecuteScriptAndExtractDouble(parent_iframe_node->current_frame_host(),
"window.domAutomationController.send("
"document.body.scrollTop);",
&div_scroll_top));
EXPECT_NE(div_scroll_top_start, div_scroll_top);
// Verify the non-fast scrollable region rect is the same, even though the
// parent scroll isn't.
EXPECT_TRUE(ExecuteScriptAndExtractInt(
parent_iframe_node->current_frame_host(),
"window.internals.markGestureScrollRegionDirty(document);\n"
"window.internals.forceCompositingUpdate(document);\n"
"var rects = window.internals.nonFastScrollableRects(document);\n"
"window.domAutomationController.send(rects.length);",
&num_non_fast_region_rects));
EXPECT_EQ(1, num_non_fast_region_rects);
EXPECT_TRUE(ExecuteScriptAndExtractString(
parent_iframe_node->current_frame_host(),
"var rect = {\n"
" x: rects[0].left,\n"
" y: rects[0].top,\n"
" width: rects[0].width,\n"
" height: rects[0].height\n"
"};\n"
"window.domAutomationController.send(JSON.stringify(rect));",
&str));
gfx::Rect non_fast_scrollable_rect_after_scroll;
ConvertJSONToRect(str, &non_fast_scrollable_rect_after_scroll);
EXPECT_EQ(non_fast_scrollable_rect_before_scroll,
non_fast_scrollable_rect_after_scroll);
}
#endif // defined(USE_AURA)
// Tests that wheel scroll bubbling gets cancelled when the wheel target view
// gets destroyed in the middle of a wheel scroll seqeunce. This happens in
// cases like overscroll navigation from inside an oopif.
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
CancelWheelScrollBubblingOnWheelTargetDeletion) {
ui::GestureConfiguration::GetInstance()->set_scroll_debounce_interval_in_ms(
0);
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
RenderFrameSubmissionObserver render_frame_submission_observer(
shell()->web_contents());
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* iframe_node = root->child_at(0);
GURL site_url(embedded_test_server()->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, iframe_node->current_url());
RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* child_rwhv = static_cast<RenderWidgetHostViewBase*>(
iframe_node->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostInputEventRouter* router =
static_cast<WebContentsImpl*>(shell()->web_contents())
->GetInputEventRouter();
WaitForHitTestDataOrChildSurfaceReady(iframe_node->current_frame_host());
InputEventAckWaiter scroll_begin_observer(
root->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kGestureScrollBegin);
InputEventAckWaiter scroll_end_observer(
root->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kGestureScrollEnd);
// Scroll the iframe upward, scroll events get bubbled up to the root.
blink::WebMouseWheelEvent scroll_event(
blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
gfx::Rect bounds = child_rwhv->GetViewBounds();
float scale_factor =
render_frame_submission_observer.LastRenderFrameMetadata()
.page_scale_factor;
gfx::Point position_in_widget(
gfx::ToCeiledInt((bounds.x() - root_view->GetViewBounds().x() + 5) *
scale_factor),
gfx::ToCeiledInt((bounds.y() - root_view->GetViewBounds().y() + 5) *
scale_factor));
SetWebEventPositions(&scroll_event, position_in_widget, root_view);
scroll_event.delta_x = 0.0f;
scroll_event.delta_y = 5.0f;
scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan;
scroll_event.has_precise_scrolling_deltas = true;
router->RouteMouseWheelEvent(root_view, &scroll_event, ui::LatencyInfo());
scroll_begin_observer.Wait();
// Now destroy the child_rwhv, scroll bubbling stops and a GSE gets sent to
// the root_view.
RenderProcessHost* rph =
iframe_node->current_frame_host()->GetSiteInstance()->GetProcess();
RenderProcessHostWatcher crash_observer(
rph, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
EXPECT_TRUE(rph->Shutdown(0));
crash_observer.Wait();
scroll_event.delta_y = 0.0f;
scroll_event.phase = blink::WebMouseWheelEvent::kPhaseEnded;
scroll_event.dispatch_type =
blink::WebInputEvent::DispatchType::kEventNonBlocking;
router->RouteMouseWheelEvent(root_view, &scroll_event, ui::LatencyInfo());
scroll_end_observer.Wait();
}
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();
}
class SitePerProcessEmulatedTouchBrowserTest
: public SitePerProcessHitTestBrowserTest {
public:
enum TestType {
ScrollBubbling,
PinchGoesToMainFrame,
TouchActionBubbling,
ShowPressHasTouchID
};
~SitePerProcessEmulatedTouchBrowserTest() override {}
void RunTest(TestType test_type) {
std::string url;
if (test_type == TouchActionBubbling)
url = "/frame_tree/page_with_pany_frame.html";
else
url = "/frame_tree/page_with_positioned_frame.html";
GURL main_url(embedded_test_server()->GetURL(url));
ASSERT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* iframe_node = root->child_at(0);
GURL site_url(embedded_test_server()->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, iframe_node->current_url());
RenderWidgetHostViewBase* root_rwhv =
static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* child_rwhv =
static_cast<RenderWidgetHostViewBase*>(iframe_node->current_frame_host()
->GetRenderWidgetHost()
->GetView());
RenderWidgetHostInputEventRouter* router =
static_cast<WebContentsImpl*>(shell()->web_contents())
->GetInputEventRouter();
WaitForHitTestDataOrChildSurfaceReady(iframe_node->current_frame_host());
auto expect_gesture_with_position = base::BindRepeating(
[](blink::WebInputEvent::Type expected_type,
const gfx::Point& expected_position, content::InputEventAckSource,
content::InputEventAckState, const blink::WebInputEvent& event) {
if (event.GetType() != expected_type)
return false;
const blink::WebGestureEvent& gesture_event =
static_cast<const blink::WebGestureEvent&>(event);
EXPECT_NEAR(expected_position.x(), gesture_event.PositionInWidget().x,
kHitTestTolerance);
EXPECT_NEAR(expected_position.y(), gesture_event.PositionInWidget().y,
kHitTestTolerance);
EXPECT_EQ(blink::kWebGestureDeviceTouchscreen,
gesture_event.SourceDevice());
// We expect all gesture events to have non-zero ids otherwise they
// can force hit-testing in RenderWidgetHostInputEventRouter even
// when it's unnecessary.
EXPECT_NE(0U, gesture_event.unique_touch_event_id);
return true;
});
blink::WebInputEvent::Type expected_gesture_type;
switch (test_type) {
case ScrollBubbling:
case TouchActionBubbling:
expected_gesture_type = blink::WebInputEvent::kGestureScrollBegin;
break;
case PinchGoesToMainFrame:
expected_gesture_type = blink::WebInputEvent::kGesturePinchBegin;
break;
case ShowPressHasTouchID:
expected_gesture_type = blink::WebInputEvent::kGestureShowPress;
break;
default:
ASSERT_TRUE(false);
}
#if defined(OS_WIN)
{
gfx::Rect view_bounds = root_rwhv->GetViewBounds();
LOG(ERROR) << "Root view bounds = (" << view_bounds.x() << ","
<< view_bounds.y() << ") " << view_bounds.width() << " x "
<< view_bounds.height();
}
#endif
gfx::Point position_in_child(5, 5);
InputEventAckWaiter child_gesture_event_observer(
child_rwhv->GetRenderWidgetHost(),
base::BindRepeating(expect_gesture_with_position, expected_gesture_type,
position_in_child));
gfx::Point position_in_root =
child_rwhv->TransformPointToRootCoordSpace(position_in_child);
InputEventAckWaiter root_gesture_event_observer(
root_rwhv->GetRenderWidgetHost(),
base::BindRepeating(expect_gesture_with_position, expected_gesture_type,
position_in_root));
// Enable touch emulation.
auto* touch_emulator = router->GetTouchEmulator();
ASSERT_TRUE(touch_emulator);
touch_emulator->Enable(TouchEmulator::Mode::kEmulatingTouchFromMouse,
ui::GestureProviderConfigType::CURRENT_PLATFORM);
// Create mouse events to emulate touch scroll. Since the page has no touch
// handlers, these events will be converted into a gesture scroll sequence.
base::TimeTicks simulated_event_time = ui::EventTimeForNow();
base::TimeDelta simulated_event_time_delta =
base::TimeDelta::FromMilliseconds(100);
blink::WebMouseEvent mouse_move_event =
SyntheticWebMouseEventBuilder::Build(blink::WebInputEvent::kMouseMove,
position_in_root.x(),
position_in_root.y(), 0);
mouse_move_event.SetTimeStamp(simulated_event_time);
int mouse_modifier = (test_type == PinchGoesToMainFrame)
? blink::WebInputEvent::kShiftKey
: 0;
mouse_modifier |= blink::WebInputEvent::kLeftButtonDown;
blink::WebMouseEvent mouse_down_event =
SyntheticWebMouseEventBuilder::Build(
blink::WebInputEvent::kMouseDown, position_in_root.x(),
position_in_root.y(), mouse_modifier);
mouse_down_event.button = blink::WebMouseEvent::Button::kLeft;
simulated_event_time += simulated_event_time_delta;
mouse_down_event.SetTimeStamp(simulated_event_time);
blink::WebMouseEvent mouse_drag_event =
SyntheticWebMouseEventBuilder::Build(
blink::WebInputEvent::kMouseMove, position_in_root.x(),
position_in_root.y() + 20, mouse_modifier);
simulated_event_time += simulated_event_time_delta;
mouse_drag_event.SetTimeStamp(simulated_event_time);
mouse_drag_event.button = blink::WebMouseEvent::Button::kLeft;
blink::WebMouseEvent mouse_up_event = SyntheticWebMouseEventBuilder::Build(
blink::WebInputEvent::kMouseUp, position_in_root.x(),
position_in_root.y() + 20, mouse_modifier);
mouse_up_event.button = blink::WebMouseEvent::Button::kLeft;
simulated_event_time += simulated_event_time_delta;
mouse_up_event.SetTimeStamp(simulated_event_time);
// Send mouse events and wait for GesturePinchBegin.
router->RouteMouseEvent(root_rwhv, &mouse_move_event, ui::LatencyInfo());
router->RouteMouseEvent(root_rwhv, &mouse_down_event, ui::LatencyInfo());
if (test_type == ShowPressHasTouchID) {
// Wait for child to receive GestureShowPress. If this test fails, it
// will either DCHECK or time out.
child_gesture_event_observer.Wait();
return;
}
router->RouteMouseEvent(root_rwhv, &mouse_drag_event, ui::LatencyInfo());
router->RouteMouseEvent(root_rwhv, &mouse_up_event, ui::LatencyInfo());
if (test_type == ScrollBubbling || test_type == TouchActionBubbling) {
// Verify child receives GestureScrollBegin.
child_gesture_event_observer.Wait();
}
// Verify the root receives the GesturePinchBegin or GestureScrollBegin,
// depending on |test_type|.
root_gesture_event_observer.Wait();
// Shut down.
touch_emulator->Disable();
}
};
IN_PROC_BROWSER_TEST_P(SitePerProcessEmulatedTouchBrowserTest,
EmulatedTouchShowPressHasTouchID) {
RunTest(ShowPressHasTouchID);
}
IN_PROC_BROWSER_TEST_P(SitePerProcessEmulatedTouchBrowserTest,
EmulatedTouchScrollBubbles) {
RunTest(ScrollBubbling);
}
IN_PROC_BROWSER_TEST_P(SitePerProcessEmulatedTouchBrowserTest,
EmulatedTouchPinchGoesToMainFrame) {
RunTest(PinchGoesToMainFrame);
}
IN_PROC_BROWSER_TEST_P(SitePerProcessEmulatedTouchBrowserTest,
EmulatedGestureScrollBubbles) {
RunTest(TouchActionBubbling);
}
// Regression test for https://crbug.com/851644. The test passes as long as it
// doesn't crash.
// Touch action ack timeout is enabled on Android only.
#if defined(OS_ANDROID)
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
TouchActionAckTimeout) {
GURL main_url(
embedded_test_server()->GetURL("/frame_tree/page_with_janky_frame.html"));
ASSERT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
GURL frame_url(embedded_test_server()->GetURL(
"baz.com", "/page_with_touch_start_janking_main_thread.html"));
auto* child_frame_host = root->child_at(0)->current_frame_host();
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewChildFrame* rwhv_child =
static_cast<RenderWidgetHostViewChildFrame*>(
child_frame_host->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_frame_host);
// Compute the point so that the gesture event can target the child frame.
const gfx::Rect root_bounds = rwhv_root->GetViewBounds();
const gfx::Rect child_bounds = rwhv_child->GetViewBounds();
RenderFrameSubmissionObserver render_frame_submission_observer(
shell()->web_contents());
const float page_scale_factor =
render_frame_submission_observer.LastRenderFrameMetadata()
.page_scale_factor;
const gfx::PointF point_in_child(
(child_bounds.x() - root_bounds.x() + 25) * page_scale_factor,
(child_bounds.y() - root_bounds.y() + 25) * page_scale_factor);
SyntheticSmoothScrollGestureParams params;
params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT;
params.anchor = gfx::PointF(point_in_child.x(), point_in_child.y());
params.distances.push_back(gfx::Vector2dF(0, -10));
// The JS jank from the "page_with_touch_start_janking_main_thread.html"
// causes the touch ack timeout. Set the speed high so that the gesture can be
// completed quickly and so does this test.
params.speed_in_pixels_s = 100000;
std::unique_ptr<SyntheticSmoothScrollGesture> gesture(
new SyntheticSmoothScrollGesture(params));
InputEventAckWaiter ack_observer(
child_frame_host->GetRenderWidgetHost(),
base::BindRepeating([](content::InputEventAckSource source,
content::InputEventAckState state,
const blink::WebInputEvent& event) {
return event.GetType() == blink::WebGestureEvent::kGestureScrollEnd;
}));
ack_observer.Reset();
RenderWidgetHostImpl* render_widget_host =
root->current_frame_host()->GetRenderWidgetHost();
render_widget_host->QueueSyntheticGesture(
std::move(gesture), base::BindOnce([](SyntheticGesture::Result result) {
EXPECT_EQ(SyntheticGesture::GESTURE_FINISHED, result);
}));
ack_observer.Wait();
}
#endif // defined(OS_ANDROID)
#if defined(USE_AURA) || defined(OS_ANDROID)
// When unconsumed scrolls in a child bubble to the root and start an
// overscroll gesture, the subsequent gesture scroll update events should be
// consumed by the root. The child should not be able to scroll during the
// overscroll gesture.
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
RootConsumesScrollDuringOverscrollGesture) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
RenderFrameSubmissionObserver render_frame_submission_observer(
shell()->web_contents());
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
#if defined(USE_AURA)
// The child must be horizontally scrollable.
GURL child_url(embedded_test_server()->GetURL("b.com", "/wide_page.html"));
#elif defined(OS_ANDROID)
// The child must be vertically scrollable.
GURL child_url(embedded_test_server()->GetURL("b.com", "/tall_page.html"));
#endif
NavigateFrameToURL(child_node, child_url);
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://b.com/",
DepictFrameTree(root));
RenderWidgetHostViewChildFrame* rwhv_child =
static_cast<RenderWidgetHostViewChildFrame*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
ASSERT_TRUE(rwhv_root->IsScrollOffsetAtTop());
ASSERT_TRUE(rwhv_child->IsScrollOffsetAtTop());
RenderWidgetHostInputEventRouter* router =
static_cast<WebContentsImpl*>(shell()->web_contents())
->GetInputEventRouter();
{
// Set up the RenderWidgetHostInputEventRouter to send the gesture stream
// to the child.
const gfx::Rect root_bounds = rwhv_root->GetViewBounds();
const gfx::Rect child_bounds = rwhv_child->GetViewBounds();
const float page_scale_factor =
render_frame_submission_observer.LastRenderFrameMetadata()
.page_scale_factor;
const gfx::PointF point_in_child(
(child_bounds.x() - root_bounds.x() + 10) * page_scale_factor,
(child_bounds.y() - root_bounds.y() + 10) * page_scale_factor);
gfx::PointF dont_care;
ASSERT_EQ(rwhv_child->GetRenderWidgetHost(),
router->GetRenderWidgetHostAtPoint(rwhv_root, point_in_child,
&dont_care));
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_child, rwhv_root);
touch_event.unique_touch_event_id = 1;
InputEventAckWaiter waiter(rwhv_child->GetRenderWidgetHost(),
blink::WebInputEvent::kTouchStart);
router->RouteTouchEvent(rwhv_root, &touch_event,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
// With async hit testing, make sure the target for the initial TouchStart
// is resolved before sending the rest of the stream.
waiter.Wait();
blink::WebGestureEvent gesture_event(
blink::WebInputEvent::kGestureTapDown,
blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests(),
blink::kWebGestureDeviceTouchscreen);
gesture_event.unique_touch_event_id = touch_event.unique_touch_event_id;
router->RouteGestureEvent(rwhv_root, &gesture_event,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
}
#if defined(USE_AURA)
RenderWidgetHostViewAura* rwhva =
static_cast<RenderWidgetHostViewAura*>(rwhv_root);
std::unique_ptr<MockOverscrollControllerDelegateAura>
mock_overscroll_delegate =
std::make_unique<MockOverscrollControllerDelegateAura>(rwhva);
rwhva->overscroll_controller()->set_delegate(mock_overscroll_delegate.get());
MockOverscrollObserver* mock_overscroll_observer =
mock_overscroll_delegate.get();
#elif defined(OS_ANDROID)
RenderWidgetHostViewAndroid* rwhv_android =
static_cast<RenderWidgetHostViewAndroid*>(rwhv_root);
std::unique_ptr<MockOverscrollRefreshHandlerAndroid> mock_overscroll_handler =
std::make_unique<MockOverscrollRefreshHandlerAndroid>();
rwhv_android->SetOverscrollControllerForTesting(
mock_overscroll_handler.get());
MockOverscrollObserver* mock_overscroll_observer =
mock_overscroll_handler.get();
#endif // defined(USE_AURA)
InputEventAckWaiter gesture_begin_observer_child(
child_node->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kGestureScrollBegin);
InputEventAckWaiter gesture_end_observer_child(
child_node->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kGestureScrollEnd);
#if defined(USE_AURA)
const float overscroll_threshold = OverscrollConfig::GetThreshold(
OverscrollConfig::Threshold::kStartTouchscreen);
#elif defined(OS_ANDROID)
const float overscroll_threshold = 0.f;
#endif
// First we need our scroll to initiate an overscroll gesture in the root
// via unconsumed scrolls in the child.
blink::WebGestureEvent gesture_scroll_begin(
blink::WebGestureEvent::kGestureScrollBegin,
blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests(),
blink::kWebGestureDeviceTouchscreen);
gesture_scroll_begin.unique_touch_event_id = 1;
gesture_scroll_begin.data.scroll_begin.delta_hint_units =
blink::WebGestureEvent::ScrollUnits::kPrecisePixels;
gesture_scroll_begin.data.scroll_begin.delta_x_hint = 0.f;
gesture_scroll_begin.data.scroll_begin.delta_y_hint = 0.f;
#if defined(USE_AURA)
// For aura, we scroll horizontally to activate an overscroll navigation.
gesture_scroll_begin.data.scroll_begin.delta_x_hint =
overscroll_threshold + 1;
#elif defined(OS_ANDROID)
// For android, we scroll vertically to activate pull-to-refresh.
gesture_scroll_begin.data.scroll_begin.delta_y_hint =
overscroll_threshold + 1;
#endif
router->RouteGestureEvent(rwhv_root, &gesture_scroll_begin,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
// Make sure the child is indeed receiving the gesture stream.
gesture_begin_observer_child.Wait();
blink::WebGestureEvent gesture_scroll_update(
blink::WebGestureEvent::kGestureScrollUpdate,
blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests(),
blink::kWebGestureDeviceTouchscreen);
gesture_scroll_update.unique_touch_event_id = 1;
gesture_scroll_update.data.scroll_update.delta_units =
blink::WebGestureEvent::ScrollUnits::kPrecisePixels;
gesture_scroll_update.data.scroll_update.delta_x = 0.f;
gesture_scroll_update.data.scroll_update.delta_y = 0.f;
#if defined(USE_AURA)
float* delta = &gesture_scroll_update.data.scroll_update.delta_x;
#elif defined(OS_ANDROID)
float* delta = &gesture_scroll_update.data.scroll_update.delta_y;
#endif
*delta = overscroll_threshold + 1;
mock_overscroll_observer->Reset();
// This will bring us into an overscroll gesture.
router->RouteGestureEvent(rwhv_root, &gesture_scroll_update,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
// Note that in addition to verifying that we get the overscroll update, it
// is necessary to wait before sending the next event to prevent our multiple
// GestureScrollUpdates from being coalesced.
mock_overscroll_observer->WaitForUpdate();
// This scroll is in the same direction and so it will contribute to the
// overscroll.
*delta = 10.0f;
mock_overscroll_observer->Reset();
router->RouteGestureEvent(rwhv_root, &gesture_scroll_update,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
mock_overscroll_observer->WaitForUpdate();
// Now we reverse direction. The child could scroll in this direction, but
// since we're in an overscroll gesture, the root should consume it.
*delta = -5.0f;
mock_overscroll_observer->Reset();
router->RouteGestureEvent(rwhv_root, &gesture_scroll_update,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
mock_overscroll_observer->WaitForUpdate();
blink::WebGestureEvent gesture_scroll_end(
blink::WebGestureEvent::kGestureScrollEnd,
blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests(),
blink::kWebGestureDeviceTouchscreen);
gesture_scroll_end.unique_touch_event_id = 1;
gesture_scroll_end.data.scroll_end.delta_units =
blink::WebGestureEvent::ScrollUnits::kPrecisePixels;
mock_overscroll_observer->Reset();
router->RouteGestureEvent(rwhv_root, &gesture_scroll_end,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
mock_overscroll_observer->WaitForEnd();
// Ensure that the method of providing the child's scroll events to the root
// does not leave the child in an invalid state.
gesture_end_observer_child.Wait();
}
#endif // defined(USE_AURA) || defined(OS_ANDROID)
// Test that an ET_SCROLL event sent to an out-of-process iframe correctly
// results in a scroll. This is only handled by RenderWidgetHostViewAura
// and is needed for trackpad scrolling on Chromebooks.
#if defined(USE_AURA)
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, ScrollEventToOOPIF) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server()->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewAura* rwhv_parent =
static_cast<RenderWidgetHostViewAura*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
// Create listener for input events.
TestInputEventObserver child_frame_monitor(
child_node->current_frame_host()->GetRenderWidgetHost());
// Send a ui::ScrollEvent that will hit test to the child frame.
InputEventAckWaiter waiter(
child_node->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kMouseWheel);
ui::ScrollEvent scroll_event(ui::ET_SCROLL, gfx::Point(75, 75),
ui::EventTimeForNow(), ui::EF_NONE, 0,
10, // Offsets
0, 10, // Offset ordinals
2);
UpdateEventRootLocation(&scroll_event, rwhv_parent);
rwhv_parent->OnScrollEvent(&scroll_event);
waiter.Wait();
// Verify that this a mouse wheel event was sent to the child frame renderer.
EXPECT_TRUE(child_frame_monitor.EventWasReceived());
EXPECT_TRUE(base::ContainsValue(child_frame_monitor.events_received(),
blink::WebInputEvent::kMouseWheel));
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
InputEventRouterWheelCoalesceTest) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server()->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewAura* rwhv_parent =
static_cast<RenderWidgetHostViewAura*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
RenderWidgetHostInputEventRouter* router =
web_contents()->GetInputEventRouter();
// Create listener for input events.
TestInputEventObserver child_frame_monitor(
child_node->current_frame_host()->GetRenderWidgetHost());
InputEventAckWaiter waiter(
child_node->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kMouseWheel);
// Send a mouse wheel event to child.
blink::WebMouseWheelEvent wheel_event(
blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
SetWebEventPositions(&wheel_event, gfx::Point(75, 75), rwhv_parent);
wheel_event.delta_x = 10;
wheel_event.delta_y = 20;
wheel_event.phase = blink::WebMouseWheelEvent::kPhaseBegan;
router->RouteMouseWheelEvent(rwhv_parent, &wheel_event, ui::LatencyInfo());
// Send more mouse wheel events to the child. Since we are waiting for the
// async targeting on the first event, these new mouse wheel events should
// be coalesced properly.
blink::WebMouseWheelEvent wheel_event1(
blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
SetWebEventPositions(&wheel_event1, gfx::Point(70, 70), rwhv_parent);
wheel_event1.delta_x = 12;
wheel_event1.delta_y = 22;
wheel_event1.phase = blink::WebMouseWheelEvent::kPhaseChanged;
router->RouteMouseWheelEvent(rwhv_parent, &wheel_event1, ui::LatencyInfo());
blink::WebMouseWheelEvent wheel_event2(
blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
SetWebEventPositions(&wheel_event2, gfx::Point(65, 65), rwhv_parent);
wheel_event2.delta_x = 14;
wheel_event2.delta_y = 24;
wheel_event2.phase = blink::WebMouseWheelEvent::kPhaseChanged;
router->RouteMouseWheelEvent(rwhv_parent, &wheel_event2, ui::LatencyInfo());
// Since we are targeting child, event dispatch should not happen
// synchronously. Validate that the expected target does not receive the
// event immediately.
// When V2 surface layer hit testing is enabled, we expect to do synchronous
// event targeting on a child under some circumstances, so we expect the event
// immediately dispatched to the child.
if (!features::IsVizHitTestingSurfaceLayerEnabled())
EXPECT_FALSE(child_frame_monitor.EventWasReceived());
waiter.Wait();
EXPECT_TRUE(child_frame_monitor.EventWasReceived());
EXPECT_EQ(child_frame_monitor.EventType(), blink::WebInputEvent::kMouseWheel);
// Check if the two mouse-wheel update events are coalesced correctly.
const auto& gesture_event =
static_cast<const blink::WebGestureEvent&>(child_frame_monitor.event());
EXPECT_EQ(26 /* wheel_event1.delta_x + wheel_event2.delta_x */,
gesture_event.data.scroll_update.delta_x);
EXPECT_EQ(46 /* wheel_event1.delta_y + wheel_event2.delta_y */,
gesture_event.data.scroll_update.delta_y);
}
#endif // defined(USE_AURA)
// Test that mouse events are being routed to the correct RenderWidgetHostView
// based on coordinates.
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, SurfaceHitTestTest) {
SurfaceHitTestTestHelper(shell(), embedded_test_server());
}
// Same test as above, but runs in high-dpi mode.
// NOTE: This has to be renamed from SurfaceHitTestTest to
// HighDPISurfaceHitTestTest. Otherwise MAYBE_SurfaceHitTestTest gets #defined
// twice.
#if defined(OS_ANDROID) || defined(OS_WIN)
// High DPI browser tests are not needed on Android, and confuse some of the
// coordinate calculations. Android uses fixed device scale factor.
// Windows is disabled because of https://crbug.com/545547.
#define MAYBE_HighDPISurfaceHitTestTest DISABLED_HighDPISurfaceHitTestTest
#else
#define MAYBE_HighDPISurfaceHitTestTest HighDPISurfaceHitTestTest
#endif
IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest,
MAYBE_HighDPISurfaceHitTestTest) {
SurfaceHitTestTestHelper(shell(), embedded_test_server());
}
// Test that mouse events are being routed to the correct RenderWidgetHostView
// when there are nested out-of-process iframes.
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
NestedSurfaceHitTestTest) {
NestedSurfaceHitTestTestHelper(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest,
NestedSurfaceHitTestTest) {
NestedSurfaceHitTestTestHelper(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
NonFlatTransformedSurfaceHitTestTest) {
NonFlatTransformedSurfaceHitTestHelper(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest,
NonFlatTransformedSurfaceHitTestTest) {
NonFlatTransformedSurfaceHitTestHelper(shell(), embedded_test_server());
}
// TODO(kenrb): Running this test on Android bots has slight discrepancies in
// transformed event coordinates when we do manual calculation of expected
// values. We can't rely on browser side transformation because it is broken
// for perspective transforms. See https://crbug.com/854247.
#if defined(OS_ANDROID)
#define MAYBE_PerspectiveTransformedSurfaceHitTestTest \
DISABLED_PerspectiveTransformedSurfaceHitTestTest
#else
#define MAYBE_PerspectiveTransformedSurfaceHitTestTest \
PerspectiveTransformedSurfaceHitTestTest
#endif
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
MAYBE_PerspectiveTransformedSurfaceHitTestTest) {
PerspectiveTransformedSurfaceHitTestHelper(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest,
MAYBE_PerspectiveTransformedSurfaceHitTestTest) {
PerspectiveTransformedSurfaceHitTestHelper(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest,
OverlapSurfaceHitTestTest) {
OverlapSurfaceHitTestHelper(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
OverlapSurfaceHitTestTest) {
OverlapSurfaceHitTestHelper(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
HitTestLayerSquashing) {
HitTestLayerSquashing(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest,
HitTestLayerSquashing) {
HitTestLayerSquashing(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, HitTestWatermark) {
HitTestWatermark(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest,
HitTestWatermark) {
HitTestWatermark(shell(), embedded_test_server());
}
#if defined(USE_AURA)
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, RootWindowTransform) {
HitTestRootWindowTransform(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest,
RootWindowTransform) {
HitTestRootWindowTransform(shell(), embedded_test_server());
}
#endif // defined(USE_AURA)
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
HitTestStaleDataDeletedView) {
// Have two iframes to avoid going to short circuit path during the second
// targeting.
GURL main_url(
embedded_test_server()->GetURL("/frame_tree/page_with_two_iframes.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
auto* web_contents = static_cast<WebContentsImpl*>(shell()->web_contents());
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(2U, root->child_count());
FrameTreeNode* child_node1 = root->child_at(0);
GURL site_url1(embedded_test_server()->GetURL("bar.com", "/title1.html"));
EXPECT_EQ(site_url1, child_node1->current_url());
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
child_node1->current_frame_host()->GetSiteInstance());
FrameTreeNode* child_node2 = root->child_at(1);
GURL site_url2(embedded_test_server()->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url2, child_node2->current_url());
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
child_node2->current_frame_host()->GetSiteInstance());
RenderWidgetHostImpl* root_rwh = static_cast<RenderWidgetHostImpl*>(
root->current_frame_host()->GetRenderWidgetHost());
RenderWidgetHostViewBase* rwhv_parent =
static_cast<RenderWidgetHostViewBase*>(root_rwh->GetView());
RenderWidgetHostViewBase* rwhv_child2 =
static_cast<RenderWidgetHostViewBase*>(
child_node2->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node1->current_frame_host());
WaitForHitTestDataOrChildSurfaceReady(child_node2->current_frame_host());
const gfx::PointF child_location(50, 50);
gfx::PointF parent_location =
rwhv_child2->TransformPointToRootCoordSpaceF(child_location);
// Send a mouse-down at the center of the child2. This should go to the
// child2.
DispatchMouseEventAndWaitUntilDispatch(
web_contents, rwhv_parent, parent_location, rwhv_child2, child_location);
// Remove the iframe from the page. Add an infinite loop at the end so that
// renderer wouldn't submit updated hit-test data.
FrameDeletedObserver delete_observer(child_node2->current_frame_host());
ExecuteScriptAsync(
root,
"document.body.removeChild(document.getElementsByName('frame2')[0]);"
"while(true) {}");
delete_observer.Wait();
EXPECT_EQ(1U, root->child_count());
// The synchronous targeting for the same location should now find the
// root-view as the target (and require async-targeting), since child2 has
// been removed. We cannot actually attempt to dispatch the event though,
// since it would try to do asynchronous targeting by asking the root-view,
// whose main-thread is blocked because of the infinite-loop in the injected
// javascript above.
blink::WebMouseEvent down_event(
blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
down_event.button = blink::WebPointerProperties::Button::kLeft;
down_event.click_count = 1;
SetWebEventPositions(&down_event, parent_location, rwhv_parent);
auto result = web_contents->GetInputEventRouter()->FindTargetSynchronously(
rwhv_parent, down_event);
EXPECT_EQ(result.view, rwhv_parent);
// When VizHitTestSurfaceLayer is enabled and there is only one child frame,
// we can find the target frame and are sure there are no other possible
// targets, in this case, we dispatch the event immediately without
// asynchronously querying the root-view.
if (features::IsVizHitTestingSurfaceLayerEnabled())
EXPECT_FALSE(result.should_query_view);
else
EXPECT_TRUE(result.should_query_view);
EXPECT_EQ(result.target_location.value(), parent_location);
}
// This test tests that browser process hittesting ignores frames with
// pointer-events: none.
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
SurfaceHitTestPointerEventsNoneChanged) {
// In /2 hit testing, OOPIFs with pointer-events: none are ignored and no hit
// test data is submitted. To make sure we wait enough time until child frame
// fully loaded, we add a 1x1 pixel OOPIF for the test to track the process of
// /2 hit testing.
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_frame_pointer-events_none.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(2U, root->child_count());
FrameTreeNode* child_node1 = root->child_at(0);
FrameTreeNode* child_node2 = root->child_at(1);
GURL site_url(embedded_test_server()->GetURL("bar.com", "/title1.html"));
EXPECT_EQ(site_url, child_node2->current_url());
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
child_node2->current_frame_host()->GetSiteInstance());
// Create listeners for mouse events.
RenderWidgetHostMouseEventMonitor main_frame_monitor(
root->current_frame_host()->GetRenderWidgetHost());
RenderWidgetHostMouseEventMonitor child_frame_monitor(
child_node1->current_frame_host()->GetRenderWidgetHost());
RenderWidgetHostInputEventRouter* router =
web_contents()->GetInputEventRouter();
RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node2->current_frame_host());
// Target input event to child1 frame.
blink::WebMouseEvent child_event(
blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
child_event.button = blink::WebPointerProperties::Button::kLeft;
SetWebEventPositions(&child_event, gfx::Point(75, 75), root_view);
child_event.click_count = 1;
main_frame_monitor.ResetEventReceived();
child_frame_monitor.ResetEventReceived();
InputEventAckWaiter waiter(root->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kMouseDown);
router->RouteMouseEvent(root_view, &child_event, ui::LatencyInfo());
waiter.Wait();
EXPECT_TRUE(main_frame_monitor.EventWasReceived());
EXPECT_NEAR(75, main_frame_monitor.event().PositionInWidget().x,
kHitTestTolerance);
EXPECT_NEAR(75, main_frame_monitor.event().PositionInWidget().y,
kHitTestTolerance);
EXPECT_FALSE(child_frame_monitor.EventWasReceived());
// Surface hit test can only learn about pointer-events changes when
// submitting compositing frame, so we disable the second half of the test for
// surface hit test.
if (!features::IsVizHitTestingEnabled())
return;
// Remove pointer-events: none property from iframe, also remove child2 to
// properly notify the observer the update.
// Wait for the confirmation of the deletion so that surface hit test is aware
// of the change of pointer-events property. When viz hit testing is enabled,
// we do not need to wait.
EXPECT_TRUE(ExecuteScript(web_contents(),
"document.getElementsByTagName('iframe')[0].style."
"pointerEvents = 'auto';\n"));
ASSERT_EQ(2U, root->child_count());
{
MainThreadFrameObserver observer(
root->current_frame_host()->GetRenderWidgetHost());
observer.Wait();
}
WaitForHitTestDataOrChildSurfaceReady(child_node1->current_frame_host());
WaitForHitTestDataOrChildSurfaceReady(child_node2->current_frame_host());
main_frame_monitor.ResetEventReceived();
child_frame_monitor.ResetEventReceived();
InputEventAckWaiter child_waiter(
child_node1->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kMouseDown);
router->RouteMouseEvent(root_view, &child_event, ui::LatencyInfo());
child_waiter.Wait();
EXPECT_TRUE(child_frame_monitor.EventWasReceived());
EXPECT_NEAR(23, child_frame_monitor.event().PositionInWidget().x,
kHitTestTolerance);
EXPECT_NEAR(23, child_frame_monitor.event().PositionInWidget().y,
kHitTestTolerance);
}
// This test tests that browser process can successfully hit test on nested
// OOPIFs that are partially occluded by main frame elements.
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
HitTestNestedOccludedOOPIF) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_nested_frames_and_occluding_div.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell()->web_contents());
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* parent = root->child_at(0);
GURL site_url(embedded_test_server()->GetURL(
"bar.com", "/frame_tree/page_with_positioned_frame.html"));
EXPECT_EQ(site_url, parent->current_url());
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
parent->current_frame_host()->GetSiteInstance());
ASSERT_EQ(1U, parent->child_count());
FrameTreeNode* child = parent->child_at(0);
GURL child_site_url(
embedded_test_server()->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(child_site_url, child->current_url());
RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* child_view = static_cast<RenderWidgetHostViewBase*>(
child->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child->current_frame_host());
// Target input event to the overlapping region of main frame's div and child
// frame.
DispatchMouseEventAndWaitUntilDispatch(web_contents, root_view,
gfx::PointF(75, 75), root_view,
gfx::PointF(75, 75));
// Target input event to the non overlapping region of child frame.
// The div has a bound of (0, 0, 100, 100) with a border-radius of 5px, so
// point (99, 99) should not hit test the div but reach the nested child
// frame.
// The parent frame and child frame both have a default offset of (2, 2) and
// child frame's top and left properties are set to be (50, 50), so there is
// an offset of (54, 54) in total.
DispatchMouseEventAndWaitUntilDispatch(web_contents, root_view,
gfx::PointF(99, 99), child_view,
gfx::PointF(45, 45));
}
// Verify that an event is properly retargeted to the main frame when an
// asynchronous hit test to the child frame times out.
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
AsynchronousHitTestChildTimeout) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_busy_frame.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
// Create listeners for mouse events.
RenderWidgetHostMouseEventMonitor main_frame_monitor(
root->current_frame_host()->GetRenderWidgetHost());
RenderWidgetHostMouseEventMonitor child_frame_monitor(
child_node->current_frame_host()->GetRenderWidgetHost());
EXPECT_EQ(
" Site A ------------ proxies for B C\n"
" +--Site B ------- proxies for A C\n"
" +--Site C -- proxies for A B\n"
"Where A = http://127.0.0.1/\n"
" B = http://baz.com/\n"
" C = http://bar.com/",
DepictFrameTree(root));
RenderWidgetHostInputEventRouter* router =
web_contents()->GetInputEventRouter();
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
// Shorten the timeout for purposes of this test.
router->GetRenderWidgetTargeterForTests()
->set_async_hit_test_timeout_delay_for_testing(base::TimeDelta());
RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
EXPECT_TRUE(ExecuteScript(child_node, "lookBusy();"));
// Target input event to child frame. It should get delivered to the main
// frame instead because the child frame main thread is non-responsive.
blink::WebMouseEvent child_event(
blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
child_event.button = blink::WebPointerProperties::Button::kLeft;
SetWebEventPositions(&child_event, gfx::Point(75, 75), root_view);
child_event.click_count = 1;
main_frame_monitor.ResetEventReceived();
child_frame_monitor.ResetEventReceived();
RouteMouseEventAndWaitUntilDispatch(router, root_view, root_view,
&child_event);
EXPECT_TRUE(main_frame_monitor.EventWasReceived());
EXPECT_NEAR(75, main_frame_monitor.event().PositionInWidget().x,
kHitTestTolerance);
EXPECT_NEAR(75, main_frame_monitor.event().PositionInWidget().y,
kHitTestTolerance);
EXPECT_FALSE(child_frame_monitor.EventWasReceived());
}
// Tooltips aren't used on Android, so no need to compile/run this test in that
// case.
#if !defined(OS_ANDROID)
class TooltipMonitor : public CursorManager::TooltipObserver {
public:
TooltipMonitor(CursorManager* cursor_manager) : run_loop_(new base::RunLoop) {
DCHECK(cursor_manager);
cursor_manager->SetTooltipObserverForTesting(this);
}
~TooltipMonitor() override {}
void Reset() {
run_loop_.reset(new base::RunLoop);
tooltips_received_.clear();
}
void OnSetTooltipTextForView(const RenderWidgetHostViewBase* view,
const base::string16& tooltip_text) override {
tooltips_received_.push_back(tooltip_text);
if (tooltip_text == tooltip_text_wanted_ && run_loop_->running())
run_loop_->Quit();
}
void WaitUntil(const base::string16& tooltip_text) {
tooltip_text_wanted_ = tooltip_text;
if (base::ContainsValue(tooltips_received_, tooltip_text))
return;
run_loop_->Run();
}
private:
std::unique_ptr<base::RunLoop> run_loop_;
base::string16 tooltip_text_wanted_;
std::vector<base::string16> tooltips_received_;
DISALLOW_COPY_AND_ASSIGN(TooltipMonitor);
}; // class TooltipMonitor
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
CrossProcessTooltipTest) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://b.com/",
DepictFrameTree(root));
FrameTreeNode* b_node = root->child_at(0);
RenderWidgetHostViewBase* rwhv_a = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_b = static_cast<RenderWidgetHostViewBase*>(
b_node->current_frame_host()->GetRenderWidgetHost()->GetView());
DCHECK(rwhv_a->GetCursorManager());
TooltipMonitor tooltip_monitor(rwhv_a->GetCursorManager());
WaitForHitTestDataOrChildSurfaceReady(b_node->current_frame_host());
// Make sure the point_in_a_frame value is outside the default 8px margin
// for the body element.
gfx::Point point_in_a_frame(10, 10);
gfx::Point point_in_b_frame =
rwhv_b->TransformPointToRootCoordSpace(gfx::Point(25, 25));
// Create listeners for mouse events. These are used to verify that the
// RenderWidgetHostInputEventRouter is generating MouseLeave, etc for
// the right renderers.
RenderWidgetHostMouseEventMonitor a_frame_monitor(
root->current_frame_host()->GetRenderWidgetHost());
RenderWidgetHostMouseEventMonitor b_frame_monitor(
b_node->current_frame_host()->GetRenderWidgetHost());
// Add tooltip text to both the body and the iframe in A.
std::string script =
"body = document.body.setAttribute('title', 'body_tooltip');\n"
"iframe = document.getElementsByTagName('iframe')[0];\n"
"iframe.setAttribute('title','iframe_for_b');";
EXPECT_TRUE(ExecuteScript(root->current_frame_host(), script));
// Send mouse events to both A and B.
blink::WebMouseEvent mouse_event(
blink::WebInputEvent::kMouseMove, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
auto* router = web_contents()->GetInputEventRouter();
// Alternate mouse moves between main frame and the cross-process iframe to
// test that the tool tip in the iframe can override the one set by the main
// frame renderer, even on a second entry into the iframe.
gfx::Point current_point;
for (int iteration = 0; iteration < 2; ++iteration) {
// The following is a bit of a hack to prevent hitting the same
// position/node check in ChromeClient::SetToolTip().
current_point = point_in_a_frame;
current_point.Offset(iteration, iteration);
SetWebEventPositions(&mouse_event, current_point, rwhv_a);
RouteMouseEventAndWaitUntilDispatch(router, rwhv_a, rwhv_a, &mouse_event);
EXPECT_TRUE(a_frame_monitor.EventWasReceived());
a_frame_monitor.ResetEventReceived();
// B will receive a mouseLeave on all but the first iteration.
EXPECT_EQ(iteration != 0, b_frame_monitor.EventWasReceived());
b_frame_monitor.ResetEventReceived();
tooltip_monitor.WaitUntil(base::UTF8ToUTF16("body_tooltip"));
tooltip_monitor.Reset();
// Next send a MouseMove to B frame, and A should receive a MouseMove event.
current_point = point_in_b_frame;
current_point.Offset(iteration, iteration);
SetWebEventPositions(&mouse_event, current_point, rwhv_a);
RouteMouseEventAndWaitUntilDispatch(router, rwhv_a, rwhv_b, &mouse_event);
EXPECT_TRUE(a_frame_monitor.EventWasReceived());
EXPECT_EQ(a_frame_monitor.event().GetType(),
blink::WebInputEvent::kMouseMove);
a_frame_monitor.ResetEventReceived();
EXPECT_TRUE(b_frame_monitor.EventWasReceived());
b_frame_monitor.ResetEventReceived();
tooltip_monitor.WaitUntil(base::string16());
tooltip_monitor.Reset();
}
rwhv_a->GetCursorManager()->SetTooltipObserverForTesting(nullptr);
}
#endif // !defined(OS_ANDROID)
#if defined(OS_ANDROID)
// The following test ensures that we don't get a crash if a tooltip is
// triggered on Android. This test is nearly identical to
// SitePerProcessHitTestBrowserTest.CrossProcessTooltipTestAndroid, except
// it omits the tooltip monitor, and all dereferences of GetCursorManager().
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
CrossProcessTooltipTestAndroid) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://b.com/",
DepictFrameTree(root));
FrameTreeNode* b_node = root->child_at(0);
RenderWidgetHostViewBase* rwhv_a = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());