blob: ba3897f01c9f96bdecc52eb94f1252067b44ae47 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/site_per_process_browsertest.h"
#include <tuple>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/json/json_reader.h"
#include "base/stl_util.h"
#include "base/task/post_task.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_timeouts.h"
#include "build/build_config.h"
#include "components/viz/common/features.h"
#include "components/viz/test/host_frame_sink_manager_test_api.h"
#include "content/browser/compositor/surface_utils.h"
#include "content/browser/renderer_host/cursor_manager.h"
#include "content/browser/renderer_host/input/synthetic_smooth_scroll_gesture.h"
#include "content/browser/renderer_host/input/synthetic_tap_gesture.h"
#include "content/browser/renderer_host/input/synthetic_touchpad_pinch_gesture.h"
#include "content/browser/renderer_host/input/touch_emulator.h"
#include "content/browser/renderer_host/render_widget_host_input_event_router.h"
#include "content/browser/renderer_host/render_widget_host_view_child_frame.h"
#include "content/common/frame_messages.h"
#include "content/common/input/input_handler.mojom-test-utils.h"
#include "content/common/view_messages.h"
#include "content/common/widget_messages.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/screen_info.h"
#include "content/public/common/use_zoom_for_dsf_policy.h"
#include "content/public/common/web_preferences.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/hit_test_region_observer.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "content/shell/common/shell_switches.h"
#include "content/test/mock_overscroll_observer.h"
#include "ui/base/ui_base_features.h"
#include "ui/display/display_switches.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/gesture_detection/gesture_configuration.h"
#include "ui/events/gesture_detection/gesture_provider_config_helper.h"
#include "ui/events/platform/platform_event_source.h"
#include "ui/gfx/geometry/quad_f.h"
#if defined(USE_AURA)
#include "content/browser/renderer_host/render_widget_host_view_aura.h"
#include "content/public/browser/overscroll_configuration.h"
#include "content/test/mock_overscroll_controller_delegate_aura.h"
#include "ui/aura/window_tree_host.h"
#include "ui/events/event_rewriter.h"
#endif
#if defined(OS_MACOSX)
#include "ui/base/test/scoped_preferred_scroller_style_mac.h"
#endif
#if defined(OS_ANDROID)
#include "content/browser/renderer_host/render_widget_host_view_android.h"
#include "content/test/mock_overscroll_refresh_handler_android.h"
#endif
namespace content {
namespace {
constexpr float kHitTestTolerance = 1.f;
class TestInputEventObserver : public RenderWidgetHost::InputEventObserver {
public:
explicit TestInputEventObserver(RenderWidgetHost* host) : host_(host) {
host_->AddInputEventObserver(this);
}
~TestInputEventObserver() override { host_->RemoveInputEventObserver(this); }
bool EventWasReceived() const { return !events_received_.empty(); }
void ResetEventsReceived() { events_received_.clear(); }
blink::WebInputEvent::Type EventType() const {
DCHECK(EventWasReceived());
return events_received_.front();
}
const std::vector<blink::WebInputEvent::Type>& events_received() {
return events_received_;
}
const blink::WebInputEvent& event() const { return *event_; }
void OnInputEvent(const blink::WebInputEvent& event) override {
events_received_.push_back(event.GetType());
event_ = ui::WebInputEventTraits::Clone(event);
}
const std::vector<InputEventAckSource>& events_acked() {
return events_acked_;
}
void OnInputEventAck(InputEventAckSource source,
InputEventAckState state,
const blink::WebInputEvent&) override {
events_acked_.push_back(source);
}
private:
RenderWidgetHost* host_;
std::vector<blink::WebInputEvent::Type> events_received_;
std::vector<InputEventAckSource> events_acked_;
ui::WebScopedInputEvent event_;
DISALLOW_COPY_AND_ASSIGN(TestInputEventObserver);
};
// |position_in_widget| is in the coord space of |rwhv|.
template <typename PointType>
void SetWebEventPositions(blink::WebPointerProperties* event,
const PointType& position_in_widget,
RenderWidgetHostViewBase* rwhv,
RenderWidgetHostViewBase* rwhv_root) {
event->SetPositionInWidget(gfx::PointF(position_in_widget));
const gfx::PointF position_in_root =
rwhv->TransformPointToRootCoordSpaceF(event->PositionInWidget());
const gfx::PointF point_in_screen =
position_in_root + rwhv_root->GetViewBounds().OffsetFromOrigin();
event->SetPositionInScreen(point_in_screen.x(), point_in_screen.y());
}
// For convenience when setting the position in the space of the root RWHV.
template <typename PointType>
void SetWebEventPositions(blink::WebPointerProperties* event,
const PointType& position_in_widget,
RenderWidgetHostViewBase* rwhv_root) {
DCHECK(!rwhv_root->IsRenderWidgetHostViewChildFrame());
SetWebEventPositions(event, position_in_widget, rwhv_root, rwhv_root);
}
#if defined(USE_AURA)
// |event->location()| is in the coord space of |rwhv|.
void UpdateEventRootLocation(ui::LocatedEvent* event,
RenderWidgetHostViewBase* rwhv,
RenderWidgetHostViewBase* rwhv_root) {
const gfx::Point position_in_root =
rwhv->TransformPointToRootCoordSpace(event->location());
gfx::Point root_location = position_in_root;
aura::Window::ConvertPointToTarget(
rwhv_root->GetNativeView(), rwhv_root->GetNativeView()->GetRootWindow(),
&root_location);
event->set_root_location(root_location);
}
// For convenience when setting the position in the space of the root RWHV.
void UpdateEventRootLocation(ui::LocatedEvent* event,
RenderWidgetHostViewBase* rwhv_root) {
DCHECK(!rwhv_root->IsRenderWidgetHostViewChildFrame());
UpdateEventRootLocation(event, rwhv_root, rwhv_root);
}
#endif // defined(USE_AURA)
void RouteMouseEventAndWaitUntilDispatch(
RenderWidgetHostInputEventRouter* router,
RenderWidgetHostViewBase* root_view,
RenderWidgetHostViewBase* expected_target,
blink::WebMouseEvent* event) {
InputEventAckWaiter waiter(expected_target->GetRenderWidgetHost(),
event->GetType());
router->RouteMouseEvent(root_view, event, ui::LatencyInfo());
waiter.Wait();
}
// Dispatch |event| to the specified view using browser process hit testing.
void DispatchMouseEventAndWaitUntilDispatch(
WebContentsImpl* web_contents,
blink::WebMouseEvent& event,
RenderWidgetHostViewBase* location_view,
const gfx::PointF& location,
RenderWidgetHostViewBase* expected_target,
const gfx::PointF& expected_location) {
auto* router = web_contents->GetInputEventRouter();
RenderWidgetHostMouseEventMonitor monitor(
expected_target->GetRenderWidgetHost());
gfx::PointF root_location =
location_view->TransformPointToRootCoordSpaceF(location);
FrameTreeNode* root = web_contents->GetFrameTree()->root();
auto* root_view = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
SetWebEventPositions(&event, root_location, root_view);
RouteMouseEventAndWaitUntilDispatch(router, root_view, expected_target,
&event);
EXPECT_TRUE(monitor.EventWasReceived());
EXPECT_NEAR(expected_location.x(), monitor.event().PositionInWidget().x,
kHitTestTolerance)
<< " & original location was " << location.x() << ", " << location.y()
<< " & root_location was " << root_location.x() << ", "
<< root_location.y();
EXPECT_NEAR(expected_location.y(), monitor.event().PositionInWidget().y,
kHitTestTolerance);
}
// Wrapper for the above method that creates a MouseDown to send.
void DispatchMouseEventAndWaitUntilDispatch(
WebContentsImpl* web_contents,
RenderWidgetHostViewBase* location_view,
const gfx::PointF& location,
RenderWidgetHostViewBase* expected_target,
const gfx::PointF& expected_location) {
blink::WebMouseEvent down_event(
blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
down_event.button = blink::WebPointerProperties::Button::kLeft;
down_event.click_count = 1;
DispatchMouseEventAndWaitUntilDispatch(web_contents, down_event,
location_view, location,
expected_target, expected_location);
}
// Helper function that performs a surface hittest.
void SurfaceHitTestTestHelper(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/page_with_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_child,
gfx::PointF(5, 5), rwhv_child,
gfx::PointF(5, 5));
DispatchMouseEventAndWaitUntilDispatch(
web_contents, rwhv_root, gfx::PointF(2, 2), rwhv_root, gfx::PointF(2, 2));
}
void OverlapSurfaceHitTestHelper(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/page_with_content_overlap_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
gfx::PointF parent_location = gfx::PointF(5, 5);
parent_location =
rwhv_child->TransformPointToRootCoordSpaceF(parent_location);
DispatchMouseEventAndWaitUntilDispatch(
web_contents, rwhv_child, gfx::PointF(5, 5), rwhv_root, parent_location);
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_child,
gfx::PointF(95, 95), rwhv_child,
gfx::PointF(95, 95));
}
void NonFlatTransformedSurfaceHitTestHelper(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/page_with_non_flat_transformed_frame.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_child,
gfx::PointF(5, 5), rwhv_child,
gfx::PointF(5, 5));
}
void PerspectiveTransformedSurfaceHitTestHelper(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/page_with_perspective_transformed_frame.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
RenderFrameSubmissionObserver render_frame_submission_observer(web_contents);
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
// (90, 75) hit tests into the child frame that is positioned at (50, 50).
// Without other transformations this should result in a translated point
// of (40, 25), but the 45 degree 3-dimensional rotation of the frame about
// a vertical axis skews it.
// We can't allow DispatchMouseEventAndWaitUntilDispatch to compute the
// coordinates in the root space unless browser conversions with
// perspective transforms are first fixed. See https://crbug.com/854257.
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root,
gfx::PointF(90, 75), rwhv_child,
gfx::PointF(33, 23));
}
// Helper function that performs a surface hittest in nested frame.
void NestedSurfaceHitTestTestHelper(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/page_with_positioned_nested_frames.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* parent_iframe_node = root->child_at(0);
GURL site_url(embedded_test_server->GetURL(
"a.com", "/frame_tree/page_with_positioned_frame.html"));
EXPECT_EQ(site_url, parent_iframe_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
parent_iframe_node->current_frame_host()->GetSiteInstance());
FrameTreeNode* nested_iframe_node = parent_iframe_node->child_at(0);
GURL nested_site_url(embedded_test_server->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(nested_site_url, nested_iframe_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
nested_iframe_node->current_frame_host()->GetSiteInstance());
EXPECT_NE(parent_iframe_node->current_frame_host()->GetSiteInstance(),
nested_iframe_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewBase* rwhv_nested =
static_cast<RenderWidgetHostViewBase*>(
nested_iframe_node->current_frame_host()
->GetRenderWidgetHost()
->GetView());
WaitForHitTestDataOrChildSurfaceReady(
nested_iframe_node->current_frame_host());
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_nested,
gfx::PointF(10, 10), rwhv_nested,
gfx::PointF(10, 10));
}
void HitTestLayerSquashing(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/oopif_hit_test_layer_squashing.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
gfx::Vector2dF child_offset = rwhv_child->GetViewBounds().origin() -
rwhv_root->GetViewBounds().origin();
// Send a mouse-down on #B. The main-frame should receive it.
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root,
gfx::PointF(195, 11), rwhv_root,
gfx::PointF(195, 11));
// Send another event just below. The child-frame should receive it.
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root,
gfx::PointF(195, 30), rwhv_child,
gfx::PointF(195, 30) - child_offset);
// Send a mouse-down on #C.
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root,
gfx::PointF(35, 195), rwhv_root,
gfx::PointF(35, 195));
// Send a mouse-down to the right of #C so that it goes to the child frame.
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root,
gfx::PointF(55, 195), rwhv_child,
gfx::PointF(55, 195) - child_offset);
// Send a mouse-down to the right-bottom edge of the iframe.
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_root,
gfx::PointF(195, 235), rwhv_child,
gfx::PointF(195, 235) - child_offset);
}
void HitTestWatermark(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/oopif_hit_test_watermark.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
gfx::Vector2dF child_offset = rwhv_child->GetViewBounds().origin() -
rwhv_root->GetViewBounds().origin();
const gfx::PointF child_location(100, 120);
// Send a mouse-down at the center of the iframe. This should go to the
// main-frame (since there's a translucent div on top of it).
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_child,
child_location, rwhv_root,
child_location + child_offset);
// Set 'pointer-events: none' on the div.
EXPECT_TRUE(ExecuteScript(web_contents, "W.style.pointerEvents = 'none';"));
// TODO(sunxd): Re-enable this test when surface layer hit test is able to
// handle pointer-events none. See https://crbug.com/841358.
// Dispatch another event at the same location. It should reach the oopif this
// time.
if (!features::IsVizHitTestingSurfaceLayerEnabled()) {
DispatchMouseEventAndWaitUntilDispatch(
web_contents, rwhv_child, child_location, rwhv_child, child_location);
}
}
#if defined(USE_AURA)
void HitTestRootWindowTransform(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
// Apply transform to root window to test that we respect root window
// transform when transforming event location.
gfx::Transform transform;
transform.RotateAboutXAxis(180.f);
transform.Translate(0.f,
-shell->window()->GetHost()->window()->bounds().height());
shell->window()->GetHost()->SetRootTransform(transform);
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/page_with_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
DispatchMouseEventAndWaitUntilDispatch(web_contents, rwhv_child,
gfx::PointF(5, 5), rwhv_child,
gfx::PointF(5, 5));
DispatchMouseEventAndWaitUntilDispatch(
web_contents, rwhv_root, gfx::PointF(2, 2), rwhv_root, gfx::PointF(2, 2));
}
#endif // defined(USE_AURA)
#if defined(USE_AURA)
bool ConvertJSONToPoint(const std::string& str, gfx::PointF* point) {
std::unique_ptr<base::Value> value = base::JSONReader::ReadDeprecated(str);
if (!value)
return false;
base::DictionaryValue* root;
if (!value->GetAsDictionary(&root))
return false;
double x, y;
if (!root->GetDouble("x", &x))
return false;
if (!root->GetDouble("y", &y))
return false;
point->set_x(x);
point->set_y(y);
return true;
}
bool ConvertJSONToRect(const std::string& str, gfx::Rect* rect) {
std::unique_ptr<base::Value> value = base::JSONReader::ReadDeprecated(str);
if (!value)
return false;
base::DictionaryValue* root;
if (!value->GetAsDictionary(&root))
return false;
int x, y, width, height;
if (!root->GetInteger("x", &x))
return false;
if (!root->GetInteger("y", &y))
return false;
if (!root->GetInteger("width", &width))
return false;
if (!root->GetInteger("height", &height))
return false;
rect->set_x(x);
rect->set_y(y);
rect->set_width(width);
rect->set_height(height);
return true;
}
#endif // defined(USE_AURA)
// Class for intercepting SetMouseCapture messages being sent to a
// RenderWidgetHost. Note that this only works for RenderWidgetHosts that
// are attached to RenderFrameHosts, and not those for page popups, which
// use different bindings.
class SetMouseCaptureInterceptor
: public base::RefCountedThreadSafe<SetMouseCaptureInterceptor>,
public mojom::WidgetInputHandlerHostInterceptorForTesting {
public:
SetMouseCaptureInterceptor(RenderWidgetHostImpl* host)
: msg_received_(false),
capturing_(false),
host_(host),
impl_(binding().SwapImplForTesting(this)) {}
bool Capturing() const { return capturing_; }
void Wait() {
DCHECK(!run_loop_);
if (msg_received_) {
msg_received_ = false;
return;
}
run_loop_.reset(new base::RunLoop());
run_loop_->Run();
run_loop_.reset();
msg_received_ = false;
}
protected:
// mojom::WidgetInputHandlerHostInterceptorForTesting:
mojom::WidgetInputHandlerHost* GetForwardingInterface() override {
return impl_;
}
void SetMouseCapture(bool capturing) override {
capturing_ = capturing;
msg_received_ = true;
if (run_loop_)
run_loop_->Quit();
GetForwardingInterface()->SetMouseCapture(capturing);
}
private:
friend class base::RefCountedThreadSafe<SetMouseCaptureInterceptor>;
~SetMouseCaptureInterceptor() override {
binding().SwapImplForTesting(impl_);
}
mojo::Binding<mojom::WidgetInputHandlerHost>& binding() {
return static_cast<InputRouterImpl*>(host_->input_router())
->frame_host_binding_for_testing();
}
std::unique_ptr<base::RunLoop> run_loop_;
bool msg_received_;
bool capturing_;
RenderWidgetHostImpl* host_;
mojom::WidgetInputHandlerHost* impl_;
DISALLOW_COPY_AND_ASSIGN(SetMouseCaptureInterceptor);
};
#if defined(USE_AURA)
// A class to allow intercepting and discarding of all system-level events
// that might otherwise cause unpredictable behaviour in tests.
class SystemEventRewriter : public ui::EventRewriter {
public:
// Helper class to allow events to pass through for the lifetime of the
// object. Use this when tests generate events. This is needed under mash
// because the generate events reach SystemEventRewriter and will be dropped
// if there is no ScopedAllow instance.
// Note that allowing system events can cause flakiness in browser tests that
// don't expect them.
class ScopedAllow {
public:
explicit ScopedAllow(SystemEventRewriter* rewriter) : rewriter_(rewriter) {
++rewriter_->num_of_scoped_allows_;
}
~ScopedAllow() {
DCHECK_GT(rewriter_->num_of_scoped_allows_, 0);
--rewriter_->num_of_scoped_allows_;
}
private:
SystemEventRewriter* const rewriter_;
DISALLOW_COPY_AND_ASSIGN(ScopedAllow);
};
SystemEventRewriter() = default;
~SystemEventRewriter() override = default;
private:
ui::EventDispatchDetails RewriteEvent(
const ui::Event& event,
const Continuation continuation) override {
return num_of_scoped_allows_ ? SendEvent(continuation, &event)
: DiscardEvent(continuation);
}
// Count of ScopedAllow objects. When it is greater than 0, events are allowed
// to pass. Otherwise, they are discarded.
int num_of_scoped_allows_ = 0;
DISALLOW_COPY_AND_ASSIGN(SystemEventRewriter);
};
#endif
} // namespace
class SitePerProcessHitTestBrowserTest
: public testing::WithParamInterface<std::tuple<int, float>>,
public SitePerProcessBrowserTest {
public:
SitePerProcessHitTestBrowserTest() {}
#if defined(USE_AURA)
void PreRunTestOnMainThread() override {
SitePerProcessBrowserTest::PreRunTestOnMainThread();
// Disable system mouse events, which can interfere with tests.
shell()->window()->GetHost()->AddEventRewriter(&event_rewriter_);
}
void PostRunTestOnMainThread() override {
shell()->window()->GetHost()->RemoveEventRewriter(&event_rewriter_);
SitePerProcessBrowserTest::PostRunTestOnMainThread();
}
#endif
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
SitePerProcessBrowserTest::SetUpCommandLine(command_line);
ui::PlatformEventSource::SetIgnoreNativePlatformEvents(true);
const char kParam[] = "provider";
std::map<std::string, std::string> parameters;
if (std::get<0>(GetParam()) == 1) {
parameters[kParam] = "draw_quad";
feature_list_.InitAndEnableFeatureWithParameters(
features::kEnableVizHitTest, parameters);
} else if (std::get<0>(GetParam()) == 2) {
parameters[kParam] = "surface_layer";
feature_list_.InitAndEnableFeatureWithParameters(
features::kEnableVizHitTest, parameters);
} else {
feature_list_.InitWithFeatures(
{},
{features::kVizDisplayCompositor, features::kEnableVizHitTestDrawQuad,
features::kEnableVizHitTestSurfaceLayer});
}
}
base::test::ScopedFeatureList feature_list_;
#if defined(USE_AURA)
SystemEventRewriter event_rewriter_;
#endif
};
//
// SitePerProcessHighDPIHitTestBrowserTest
//
class SitePerProcessHighDPIHitTestBrowserTest
: public SitePerProcessHitTestBrowserTest {
public:
const double kDeviceScaleFactor = 2.0;
SitePerProcessHighDPIHitTestBrowserTest() {}
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
SitePerProcessHitTestBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII(
switches::kForceDeviceScaleFactor,
base::StringPrintf("%f", kDeviceScaleFactor));
}
};
//
// SitePerProcessNonIntegerScaleFactorHitTestBrowserTest
//
class SitePerProcessNonIntegerScaleFactorHitTestBrowserTest
: public SitePerProcessHitTestBrowserTest {
public:
const double kDeviceScaleFactor = 1.5;
SitePerProcessNonIntegerScaleFactorHitTestBrowserTest() {}
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
SitePerProcessHitTestBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII(
switches::kForceDeviceScaleFactor,
base::StringPrintf("%f", kDeviceScaleFactor));
}
};
// Restrict to Aura to we can use routable MouseWheel event via
// RenderWidgetHostViewAura::OnScrollEvent().
#if defined(USE_AURA)
class SitePerProcessInternalsHitTestBrowserTest
: public SitePerProcessHitTestBrowserTest {
public:
SitePerProcessInternalsHitTestBrowserTest() {}
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
SitePerProcessHitTestBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(switches::kExposeInternalsForTesting);
// Needed to guarantee the scrollable div we're testing with is not given
// its own compositing layer.
command_line->AppendSwitch(switches::kDisablePreferCompositingToLCDText);
command_line->AppendSwitchASCII(
switches::kForceDeviceScaleFactor,
base::StringPrintf("%f", std::get<1>(GetParam())));
}
};
IN_PROC_BROWSER_TEST_P(SitePerProcessInternalsHitTestBrowserTest,
ScrollNestedLocalNonFastScrollableDiv) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* parent_iframe_node = root->child_at(0);
GURL site_url(embedded_test_server()->GetURL(
"b.com", "/tall_page_with_local_iframe.html"));
NavigateFrameToURL(parent_iframe_node, site_url);
FrameTreeNode* nested_iframe_node = parent_iframe_node->child_at(0);
WaitForHitTestDataOrChildSurfaceReady(
nested_iframe_node->current_frame_host());
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
" +--Site B -- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://b.com/",
DepictFrameTree(root));
const char* get_element_location_script_fmt =
"var rect = "
"document.getElementById('%s').getBoundingClientRect();\n"
"var point = {\n"
" x: rect.left,\n"
" y: rect.top\n"
"};\n"
"window.domAutomationController.send(JSON.stringify(point));";
// Since the nested local b-frame shares the RenderWidgetHostViewChildFrame
// with the parent frame, we need to query element offsets in both documents
// before converting to root space coordinates for the wheel event.
std::string str;
EXPECT_TRUE(ExecuteScriptAndExtractString(
nested_iframe_node->current_frame_host(),
base::StringPrintf(get_element_location_script_fmt, "scrollable_div"),
&str));
gfx::PointF nested_point_f;
ConvertJSONToPoint(str, &nested_point_f);
EXPECT_TRUE(ExecuteScriptAndExtractString(
parent_iframe_node->current_frame_host(),
base::StringPrintf(get_element_location_script_fmt, "nested_frame"),
&str));
gfx::PointF parent_offset_f;
ConvertJSONToPoint(str, &parent_offset_f);
// Compute location for wheel event.
gfx::PointF point_f(parent_offset_f.x() + nested_point_f.x() + 5.f,
parent_offset_f.y() + nested_point_f.y() + 5.f);
RenderWidgetHostViewChildFrame* rwhv_nested =
static_cast<RenderWidgetHostViewChildFrame*>(
nested_iframe_node->current_frame_host()
->GetRenderWidgetHost()
->GetView());
point_f = rwhv_nested->TransformPointToRootCoordSpaceF(point_f);
RenderWidgetHostViewAura* rwhv_root = static_cast<RenderWidgetHostViewAura*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
gfx::PointF nested_in_parent;
rwhv_root->TransformPointToCoordSpaceForView(
point_f,
parent_iframe_node->current_frame_host()
->GetRenderWidgetHost()
->GetView(),
&nested_in_parent);
// Get original scroll position.
double div_scroll_top_start;
EXPECT_TRUE(ExecuteScriptAndExtractDouble(
nested_iframe_node->current_frame_host(),
"window.domAutomationController.send("
"document.getElementById('scrollable_div').scrollTop);",
&div_scroll_top_start));
EXPECT_EQ(0.0, div_scroll_top_start);
// Wait until renderer's compositor thread is synced. Otherwise the non fast
// scrollable regions won't be set when the event arrives.
MainThreadFrameObserver observer(rwhv_nested->GetRenderWidgetHost());
observer.Wait();
// Send a wheel to scroll the div.
gfx::Point location(point_f.x(), point_f.y());
ui::ScrollEvent scroll_event(ui::ET_SCROLL, location, ui::EventTimeForNow(),
0, 0, -ui::MouseWheelEvent::kWheelDelta, 0,
ui::MouseWheelEvent::kWheelDelta,
2); // This must be '2' or it gets silently
// dropped.
UpdateEventRootLocation(&scroll_event, rwhv_root);
InputEventAckWaiter ack_observer(
parent_iframe_node->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kGestureScrollUpdate);
rwhv_root->OnScrollEvent(&scroll_event);
ack_observer.Wait();
// Check compositor layers.
EXPECT_TRUE(ExecuteScriptAndExtractString(
nested_iframe_node->current_frame_host(),
"window.domAutomationController.send("
"window.internals.layerTreeAsText(document));",
&str));
// We expect the nested OOPIF to not have any compositor layers.
EXPECT_EQ(std::string(), str);
// Verify the div scrolled.
double div_scroll_top = div_scroll_top_start;
EXPECT_TRUE(ExecuteScriptAndExtractDouble(
nested_iframe_node->current_frame_host(),
"window.domAutomationController.send("
"document.getElementById('scrollable_div').scrollTop);",
&div_scroll_top));
EXPECT_NE(div_scroll_top_start, div_scroll_top);
}
IN_PROC_BROWSER_TEST_P(SitePerProcessInternalsHitTestBrowserTest,
NestedLocalNonFastScrollableDivCoordsAreLocal) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* parent_iframe_node = root->child_at(0);
GURL site_url(embedded_test_server()->GetURL(
"b.com", "/tall_page_with_local_iframe.html"));
NavigateFrameToURL(parent_iframe_node, site_url);
FrameTreeNode* nested_iframe_node = parent_iframe_node->child_at(0);
WaitForHitTestDataOrChildSurfaceReady(
nested_iframe_node->current_frame_host());
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
" +--Site B -- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://b.com/",
DepictFrameTree(root));
const char* get_element_location_script_fmt =
"var rect = "
"document.getElementById('%s').getBoundingClientRect();\n"
"var point = {\n"
" x: rect.left,\n"
" y: rect.top\n"
"};\n"
"window.domAutomationController.send(JSON.stringify(point));";
// Since the nested local b-frame shares the RenderWidgetHostViewChildFrame
// with the parent frame, we need to query element offsets in both documents
// before converting to root space coordinates for the wheel event.
std::string str;
EXPECT_TRUE(ExecuteScriptAndExtractString(
nested_iframe_node->current_frame_host(),
base::StringPrintf(get_element_location_script_fmt, "scrollable_div"),
&str));
gfx::PointF nested_point_f;
ConvertJSONToPoint(str, &nested_point_f);
int num_non_fast_region_rects;
EXPECT_TRUE(ExecuteScriptAndExtractInt(
parent_iframe_node->current_frame_host(),
"window.internals.markGestureScrollRegionDirty(document);\n"
"window.internals.forceCompositingUpdate(document);\n"
"var rects = window.internals.nonFastScrollableRects(document);\n"
"window.domAutomationController.send(rects.length);",
&num_non_fast_region_rects));
EXPECT_EQ(1, num_non_fast_region_rects);
EXPECT_TRUE(ExecuteScriptAndExtractString(
parent_iframe_node->current_frame_host(),
"var rect = {\n"
" x: rects[0].left,\n"
" y: rects[0].top,\n"
" width: rects[0].width,\n"
" height: rects[0].height\n"
"};\n"
"window.domAutomationController.send(JSON.stringify(rect));",
&str));
gfx::Rect non_fast_scrollable_rect_before_scroll;
ConvertJSONToRect(str, &non_fast_scrollable_rect_before_scroll);
EXPECT_TRUE(ExecuteScriptAndExtractString(
parent_iframe_node->current_frame_host(),
base::StringPrintf(get_element_location_script_fmt, "nested_frame"),
&str));
gfx::PointF parent_offset_f;
ConvertJSONToPoint(str, &parent_offset_f);
// Compute location for wheel event to scroll the parent with respect to the
// mainframe.
gfx::PointF point_f(parent_offset_f.x() + 1.f, parent_offset_f.y() + 1.f);
RenderWidgetHostViewChildFrame* rwhv_parent =
static_cast<RenderWidgetHostViewChildFrame*>(
parent_iframe_node->current_frame_host()
->GetRenderWidgetHost()
->GetView());
point_f = rwhv_parent->TransformPointToRootCoordSpaceF(point_f);
RenderWidgetHostViewAura* rwhv_root = static_cast<RenderWidgetHostViewAura*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
gfx::PointF nested_in_parent;
rwhv_root->TransformPointToCoordSpaceForView(
point_f,
parent_iframe_node->current_frame_host()
->GetRenderWidgetHost()
->GetView(),
&nested_in_parent);
// Get original scroll position.
double div_scroll_top_start;
EXPECT_TRUE(
ExecuteScriptAndExtractDouble(parent_iframe_node->current_frame_host(),
"window.domAutomationController.send("
"document.body.scrollTop);",
&div_scroll_top_start));
EXPECT_EQ(0.0, div_scroll_top_start);
// Send a wheel to scroll the parent containing the div.
gfx::Point location(point_f.x(), point_f.y());
ui::ScrollEvent scroll_event(ui::ET_SCROLL, location, ui::EventTimeForNow(),
0, 0, -ui::MouseWheelEvent::kWheelDelta, 0,
ui::MouseWheelEvent::kWheelDelta,
2); // This must be '2' or it gets silently
// dropped.
UpdateEventRootLocation(&scroll_event, rwhv_root);
InputEventAckWaiter ack_observer(
parent_iframe_node->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kGestureScrollUpdate);
rwhv_root->OnScrollEvent(&scroll_event);
ack_observer.Wait();
MainThreadFrameObserver thread_observer(rwhv_parent->GetRenderWidgetHost());
thread_observer.Wait();
// Check compositor layers.
EXPECT_TRUE(ExecuteScriptAndExtractString(
nested_iframe_node->current_frame_host(),
"window.domAutomationController.send("
"window.internals.layerTreeAsText(document));",
&str));
// We expect the nested OOPIF to not have any compositor layers.
EXPECT_EQ(std::string(), str);
// Verify the div scrolled.
double div_scroll_top = div_scroll_top_start;
EXPECT_TRUE(
ExecuteScriptAndExtractDouble(parent_iframe_node->current_frame_host(),
"window.domAutomationController.send("
"document.body.scrollTop);",
&div_scroll_top));
EXPECT_NE(div_scroll_top_start, div_scroll_top);
// Verify the non-fast scrollable region rect is the same, even though the
// parent scroll isn't.
EXPECT_TRUE(ExecuteScriptAndExtractInt(
parent_iframe_node->current_frame_host(),
"window.internals.markGestureScrollRegionDirty(document);\n"
"window.internals.forceCompositingUpdate(document);\n"
"var rects = window.internals.nonFastScrollableRects(document);\n"
"window.domAutomationController.send(rects.length);",
&num_non_fast_region_rects));
EXPECT_EQ(1, num_non_fast_region_rects);
EXPECT_TRUE(ExecuteScriptAndExtractString(
parent_iframe_node->current_frame_host(),
"var rect = {\n"
" x: rects[0].left,\n"
" y: rects[0].top,\n"
" width: rects[0].width,\n"
" height: rects[0].height\n"
"};\n"
"window.domAutomationController.send(JSON.stringify(rect));",
&str));
gfx::Rect non_fast_scrollable_rect_after_scroll;
ConvertJSONToRect(str, &non_fast_scrollable_rect_after_scroll);
EXPECT_EQ(non_fast_scrollable_rect_before_scroll,
non_fast_scrollable_rect_after_scroll);
}
#endif // defined(USE_AURA)
// Tests that wheel scroll bubbling gets cancelled when the wheel target view
// gets destroyed in the middle of a wheel scroll seqeunce. This happens in
// cases like overscroll navigation from inside an oopif.
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
CancelWheelScrollBubblingOnWheelTargetDeletion) {
ui::GestureConfiguration::GetInstance()->set_scroll_debounce_interval_in_ms(
0);
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
RenderFrameSubmissionObserver render_frame_submission_observer(
shell()->web_contents());
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* iframe_node = root->child_at(0);
GURL site_url(embedded_test_server()->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, iframe_node->current_url());
RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* child_rwhv = static_cast<RenderWidgetHostViewBase*>(
iframe_node->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostInputEventRouter* router =
static_cast<WebContentsImpl*>(shell()->web_contents())
->GetInputEventRouter();
WaitForHitTestDataOrChildSurfaceReady(iframe_node->current_frame_host());
InputEventAckWaiter scroll_begin_observer(
root->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kGestureScrollBegin);
InputEventAckWaiter scroll_end_observer(
root->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kGestureScrollEnd);
// Scroll the iframe upward, scroll events get bubbled up to the root.
blink::WebMouseWheelEvent scroll_event(
blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
gfx::Rect bounds = child_rwhv->GetViewBounds();
float scale_factor =
render_frame_submission_observer.LastRenderFrameMetadata()
.page_scale_factor;
gfx::Point position_in_widget(
gfx::ToCeiledInt((bounds.x() - root_view->GetViewBounds().x() + 5) *
scale_factor),
gfx::ToCeiledInt((bounds.y() - root_view->GetViewBounds().y() + 5) *
scale_factor));
SetWebEventPositions(&scroll_event, position_in_widget, root_view);
scroll_event.delta_x = 0.0f;
scroll_event.delta_y = 5.0f;
scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan;
scroll_event.has_precise_scrolling_deltas = true;
router->RouteMouseWheelEvent(root_view, &scroll_event, ui::LatencyInfo());
scroll_begin_observer.Wait();
// Now destroy the child_rwhv, scroll bubbling stops and a GSE gets sent to
// the root_view.
RenderProcessHost* rph =
iframe_node->current_frame_host()->GetSiteInstance()->GetProcess();
RenderProcessHostWatcher crash_observer(
rph, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
EXPECT_TRUE(rph->Shutdown(0));
crash_observer.Wait();
scroll_event.delta_y = 0.0f;
scroll_event.phase = blink::WebMouseWheelEvent::kPhaseEnded;
scroll_event.dispatch_type =
blink::WebInputEvent::DispatchType::kEventNonBlocking;
router->RouteMouseWheelEvent(root_view, &scroll_event, ui::LatencyInfo());
scroll_end_observer.Wait();
}
// Ensure that the positions of touch events sent to cross-process subframes
// account for any change in the position of the subframe during the scroll
// sequence.
// Before the issue fix, we record the transform for root to subframe coordinate
// space and reuse it in the sequence. It is wrong if the subframe moved in the
// sequence. In this test, the point passed to subframe at the touch end (scroll
// end) would be wrong because the subframe moved in scroll.
// Suppose the offset of subframe in rootframe is (0, 0) in the test, the touch
// start position in root is (15, 15) same in subframe, then move to (15, 10)
// in rootframe and subframe it caused subframe scroll down for 5px, then touch
// release in (15, 10) same as the touch move in root frame. Before the fix the
// touch end would pass (15, 10) to subframe which should be (15, 15) in
// subframe.
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
TouchAndGestureEventPositionChange) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_tall_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
auto* root_rwhv = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
// Synchronize with the child and parent renderers to guarantee that the
// surface information required for event hit testing is ready.
RenderWidgetHostViewChildFrame* child_rwhv =
static_cast<RenderWidgetHostViewChildFrame*>(
root->child_at(0)->current_frame_host()->GetView());
WaitForHitTestDataOrChildSurfaceReady(
root->child_at(0)->current_frame_host());
RenderFrameSubmissionObserver render_frame_submission_observer(
shell()->web_contents());
RenderWidgetHostInputEventRouter* router =
web_contents()->GetInputEventRouter();
const float scale_factor =
render_frame_submission_observer.LastRenderFrameMetadata()
.page_scale_factor;
auto await_touch_event_with_position = base::BindRepeating(
[](blink::WebInputEvent::Type expected_type,
RenderWidgetHostViewBase* rwhv, gfx::PointF expected_position,
gfx::PointF expected_position_in_root, InputEventAckSource,
InputEventAckState, const blink::WebInputEvent& event) {
if (event.GetType() != expected_type)
return false;
const auto& touch_event =
static_cast<const blink::WebTouchEvent&>(event);
const gfx::PointF root_point = rwhv->TransformPointToRootCoordSpaceF(
touch_event.touches[0].PositionInWidget());
EXPECT_NEAR(touch_event.touches[0].PositionInWidget().x,
expected_position.x(), 1.0f);
EXPECT_NEAR(touch_event.touches[0].PositionInWidget().y,
expected_position.y(), 1.0f);
EXPECT_NEAR(root_point.x(), expected_position_in_root.x(), 1.0f);
EXPECT_NEAR(root_point.y(), expected_position_in_root.y(), 1.0f);
return true;
});
auto await_gesture_event_with_position = base::BindRepeating(
[](blink::WebInputEvent::Type expected_type,
RenderWidgetHostViewBase* rwhv, gfx::PointF expected_position,
gfx::PointF expected_position_in_root, InputEventAckSource,
InputEventAckState, const blink::WebInputEvent& event) {
if (event.GetType() != expected_type)
return false;
const auto& gesture_event =
static_cast<const blink::WebGestureEvent&>(event);
const gfx::PointF root_point = rwhv->TransformPointToRootCoordSpaceF(
gesture_event.PositionInWidget());
EXPECT_NEAR(gesture_event.PositionInWidget().x, expected_position.x(),
1.0f);
EXPECT_NEAR(gesture_event.PositionInWidget().y, expected_position.y(),
1.0f);
EXPECT_NEAR(root_point.x(), expected_position_in_root.x(), 1.0f);
EXPECT_NEAR(root_point.y(), expected_position_in_root.y(), 1.0f);
return true;
});
MainThreadFrameObserver thread_observer(root_rwhv->GetRenderWidgetHost());
gfx::PointF touch_start_point_in_child(15, 15);
gfx::PointF touch_move_point_in_child(15, 10);
gfx::PointF touch_start_point =
child_rwhv->TransformPointToRootCoordSpaceF(touch_start_point_in_child);
gfx::PointF touch_move_point =
child_rwhv->TransformPointToRootCoordSpaceF(touch_move_point_in_child);
// Touch start
{
blink::WebTouchEvent touch_start_event(
blink::WebInputEvent::kTouchStart, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
touch_start_event.touches_length = 1;
touch_start_event.touches[0].state = blink::WebTouchPoint::kStatePressed;
touch_start_event.touches[0].SetPositionInWidget(touch_start_point);
touch_start_event.unique_touch_event_id = 1;
InputEventAckWaiter await_begin_in_child(
child_rwhv->GetRenderWidgetHost(),
base::BindRepeating(await_touch_event_with_position,
blink::WebInputEvent::kTouchStart, child_rwhv,
touch_start_point_in_child, touch_start_point));
router->RouteTouchEvent(root_rwhv, &touch_start_event,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
await_begin_in_child.Wait();
blink::WebGestureEvent gesture_tap_event(
blink::WebInputEvent::kGestureTapDown,
blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests(),
blink::WebGestureDevice::kTouchscreen);
gesture_tap_event.unique_touch_event_id = 1;
gesture_tap_event.SetPositionInWidget(touch_start_point);
InputEventAckWaiter await_tap_in_child(
child_rwhv->GetRenderWidgetHost(),
base::BindRepeating(await_gesture_event_with_position,
blink::WebInputEvent::kGestureTapDown, child_rwhv,
touch_start_point_in_child, touch_start_point));
router->RouteGestureEvent(root_rwhv, &gesture_tap_event,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
await_tap_in_child.Wait();
}
// Touch move
{
blink::WebTouchEvent touch_move_event(
blink::WebInputEvent::kTouchMove, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
touch_move_event.touches_length = 1;
touch_move_event.touches[0].state = blink::WebTouchPoint::kStateMoved;
touch_move_event.touches[0].SetPositionInWidget(touch_move_point);
touch_move_event.unique_touch_event_id = 2;
InputEventAckWaiter await_move_in_child(
child_rwhv->GetRenderWidgetHost(),
base::BindRepeating(await_touch_event_with_position,
blink::WebInputEvent::kTouchMove, child_rwhv,
touch_move_point_in_child, touch_move_point));
router->RouteTouchEvent(root_rwhv, &touch_move_event,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
await_move_in_child.Wait();
}
// Gesture Begin and update
{
blink::WebGestureEvent gesture_scroll_begin(
blink::WebGestureEvent::kGestureScrollBegin,
blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests(),
blink::WebGestureDevice::kTouchscreen);
gesture_scroll_begin.unique_touch_event_id = 2;
gesture_scroll_begin.data.scroll_begin.delta_hint_units =
blink::WebGestureEvent::ScrollUnits::kPrecisePixels;
gesture_scroll_begin.data.scroll_begin.delta_x_hint = 0.f;
gesture_scroll_begin.data.scroll_begin.delta_y_hint = -5.f * scale_factor;
gesture_scroll_begin.SetPositionInWidget(touch_start_point);
blink::WebGestureEvent gesture_scroll_update(
blink::WebGestureEvent::kGestureScrollUpdate,
blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests(),
blink::WebGestureDevice::kTouchscreen);
gesture_scroll_update.unique_touch_event_id = 2;
gesture_scroll_update.data.scroll_update.delta_units =
blink::WebGestureEvent::ScrollUnits::kPrecisePixels;
gesture_scroll_update.data.scroll_update.delta_x = 0.f;
gesture_scroll_update.data.scroll_update.delta_y = -5.f * scale_factor;
gesture_scroll_update.SetPositionInWidget(touch_start_point);
InputEventAckWaiter await_begin_in_child(
child_rwhv->GetRenderWidgetHost(),
base::BindRepeating(await_gesture_event_with_position,
blink::WebInputEvent::kGestureScrollBegin,
child_rwhv, touch_start_point_in_child,
touch_start_point));
InputEventAckWaiter await_update_in_child(
child_rwhv->GetRenderWidgetHost(),
base::BindRepeating(await_gesture_event_with_position,
blink::WebInputEvent::kGestureScrollUpdate,
child_rwhv, touch_start_point_in_child,
touch_start_point));
InputEventAckWaiter await_update_in_root(
root_rwhv->GetRenderWidgetHost(),
base::BindRepeating(await_gesture_event_with_position,
blink::WebInputEvent::kGestureScrollUpdate,
root_rwhv, touch_start_point, touch_start_point));
router->RouteGestureEvent(root_rwhv, &gesture_scroll_begin,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
await_begin_in_child.Wait();
router->RouteGestureEvent(root_rwhv, &gesture_scroll_update,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
await_update_in_child.Wait();
await_update_in_root.Wait();
thread_observer.Wait();
}
// Touch end & Scroll end
{
blink::WebTouchEvent touch_end_event(
blink::WebInputEvent::kTouchEnd, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
touch_end_event.touches_length = 1;
touch_end_event.touches[0].state = blink::WebTouchPoint::kStateReleased;
touch_end_event.touches[0].SetPositionInWidget(touch_move_point);
touch_end_event.unique_touch_event_id = 3;
InputEventAckWaiter await_end_in_child(
child_rwhv->GetRenderWidgetHost(),
base::BindRepeating(await_touch_event_with_position,
blink::WebInputEvent::kTouchEnd, child_rwhv,
touch_start_point_in_child, touch_move_point));
router->RouteTouchEvent(root_rwhv, &touch_end_event,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
await_end_in_child.Wait();
blink::WebGestureEvent gesture_scroll_end(
blink::WebGestureEvent::kGestureScrollEnd,
blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests(),
blink::WebGestureDevice::kTouchscreen);
gesture_scroll_end.unique_touch_event_id = 3;
gesture_scroll_end.data.scroll_end.delta_units =
blink::WebGestureEvent::ScrollUnits::kPrecisePixels;
gesture_scroll_end.SetPositionInWidget(touch_move_point);
InputEventAckWaiter await_scroll_end_in_child(
child_rwhv->GetRenderWidgetHost(),
base::BindRepeating(await_gesture_event_with_position,
blink::WebInputEvent::kGestureScrollEnd, child_rwhv,
touch_start_point_in_child, touch_move_point));
router->RouteGestureEvent(root_rwhv, &gesture_scroll_end,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
await_scroll_end_in_child.Wait();
thread_observer.Wait();
}
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
CSSTransformedIframeTouchEventCoordinates) {
// This test only makes sense if viz hit testing is enabled.
if (std::get<0>(GetParam()) == 0)
return;
GURL url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_scaled_frame.html"));
ASSERT_TRUE(NavigateToURL(shell(), url));
RenderFrameSubmissionObserver render_frame_submission_observer(
shell()->web_contents());
FrameTreeNode* root_frame_tree_node = web_contents()->GetFrameTree()->root();
ASSERT_EQ(1U, root_frame_tree_node->child_count());
FrameTreeNode* child_frame_tree_node = root_frame_tree_node->child_at(0);
GURL child_url(embedded_test_server()->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(child_url, child_frame_tree_node->current_url());
auto* root_rwhv = static_cast<RenderWidgetHostViewBase*>(
root_frame_tree_node->current_frame_host()
->GetRenderWidgetHost()
->GetView());
auto* child_rwhv = static_cast<RenderWidgetHostViewBase*>(
child_frame_tree_node->current_frame_host()
->GetRenderWidgetHost()
->GetView());
WaitForHitTestDataOrChildSurfaceReady(
child_frame_tree_node->current_frame_host());
const float scale_factor =
render_frame_submission_observer.LastRenderFrameMetadata()
.page_scale_factor;
// Some basic tests on the transforms between child and root. These assume
// a CSS scale of 0.5 on the child, though should be robust to placement of
// the iframe.
float kScaleTolerance = 0.0001f;
gfx::Transform transform_to_child;
ASSERT_TRUE(
root_rwhv->GetTransformToViewCoordSpace(child_rwhv, &transform_to_child));
EXPECT_TRUE(transform_to_child.IsScaleOrTranslation());
EXPECT_NEAR(2.f / scale_factor, transform_to_child.matrix().getFloat(0, 0),
kScaleTolerance);
EXPECT_NEAR(2.f / scale_factor, transform_to_child.matrix().getFloat(1, 1),
kScaleTolerance);
gfx::PointF child_origin =
child_rwhv->TransformPointToRootCoordSpaceF(gfx::PointF());
gfx::Transform transform_from_child;
ASSERT_TRUE(child_rwhv->GetTransformToViewCoordSpace(root_rwhv,
&transform_from_child));
EXPECT_TRUE(transform_from_child.IsScaleOrTranslation());
EXPECT_NEAR(0.5f * scale_factor, transform_from_child.matrix().getFloat(0, 0),
kScaleTolerance);
EXPECT_NEAR(0.5f * scale_factor, transform_from_child.matrix().getFloat(1, 1),
kScaleTolerance);
EXPECT_EQ(child_origin.x(), transform_from_child.matrix().getFloat(0, 3));
EXPECT_EQ(child_origin.y(), transform_from_child.matrix().getFloat(1, 3));
gfx::Transform transform_child_to_child =
transform_from_child * transform_to_child;
// If the scale factor is 1.f, then this multiplication of the transform with
// its inverse will be exact, and IsIdentity will indicate that. However, if
// the scale is an arbitrary float (as on Android), then we instead compare
// element by element using EXPECT_NEAR.
if (scale_factor == 1.f) {
EXPECT_TRUE(transform_child_to_child.IsIdentity());
} else {
const float kTolerance = 0.001f;
const int kDim = 4;
for (int row = 0; row < kDim; ++row) {
for (int col = 0; col < kDim; ++col) {
EXPECT_NEAR(row == col ? 1.f : 0.f,
transform_child_to_child.matrix().getFloat(row, col),
kTolerance);
}
}
}
gfx::Transform transform_root_to_root;
ASSERT_TRUE(root_rwhv->GetTransformToViewCoordSpace(root_rwhv,
&transform_root_to_root));
EXPECT_TRUE(transform_root_to_root.IsIdentity());
// Select two points inside child, one for the touch start and a different
// one for a touch move.
gfx::PointF touch_start_point_in_child(6, 6);
gfx::PointF touch_move_point_in_child(10, 10);
gfx::PointF touch_start_point =
child_rwhv->TransformPointToRootCoordSpaceF(touch_start_point_in_child);
gfx::PointF touch_move_point =
child_rwhv->TransformPointToRootCoordSpaceF(touch_move_point_in_child);
// Install InputEventObserver on child, and collect the three events.
TestInputEventObserver child_event_observer(
child_rwhv->GetRenderWidgetHost());
InputEventAckWaiter child_touch_start_waiter(
child_rwhv->GetRenderWidgetHost(), blink::WebInputEvent::kTouchStart);
InputEventAckWaiter child_touch_move_waiter(child_rwhv->GetRenderWidgetHost(),
blink::WebInputEvent::kTouchMove);
InputEventAckWaiter child_touch_end_waiter(child_rwhv->GetRenderWidgetHost(),
blink::WebInputEvent::kTouchEnd);
// Send events and verify each one was sent to the child with correctly
// transformed event coordinates.
auto* router = web_contents()->GetInputEventRouter();
const float kCoordinateTolerance = 0.1f;
// TouchStart.
blink::WebTouchEvent touch_start_event(
blink::WebInputEvent::kTouchStart, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
touch_start_event.touches_length = 1;
touch_start_event.touches[0].state = blink::WebTouchPoint::kStatePressed;
SetWebEventPositions(&touch_start_event.touches[0], touch_start_point,
root_rwhv);
touch_start_event.unique_touch_event_id = 1;
router->RouteTouchEvent(root_rwhv, &touch_start_event,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
child_touch_start_waiter.Wait();
ASSERT_EQ(1U, child_event_observer.events_received().size());
ASSERT_EQ(blink::WebInputEvent::kTouchStart,
child_event_observer.event().GetType());
const blink::WebTouchEvent& touch_start_event_received =
static_cast<const blink::WebTouchEvent&>(child_event_observer.event());
EXPECT_NEAR(touch_start_point_in_child.x(),
touch_start_event_received.touches[0].PositionInWidget().x,
kCoordinateTolerance);
EXPECT_NEAR(touch_start_point_in_child.y(),
touch_start_event_received.touches[0].PositionInWidget().y,
kCoordinateTolerance);
// TouchMove.
blink::WebTouchEvent touch_move_event(
blink::WebInputEvent::kTouchMove, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
touch_move_event.touches_length = 1;
touch_move_event.touches[0].state = blink::WebTouchPoint::kStateMoved;
SetWebEventPositions(&touch_move_event.touches[0], touch_move_point,
root_rwhv);
touch_move_event.unique_touch_event_id = 2;
router->RouteTouchEvent(root_rwhv, &touch_move_event,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
child_touch_move_waiter.Wait();
ASSERT_EQ(2U, child_event_observer.events_received().size());
ASSERT_EQ(blink::WebInputEvent::kTouchMove,
child_event_observer.event().GetType());
const blink::WebTouchEvent& touch_move_event_received =
static_cast<const blink::WebTouchEvent&>(child_event_observer.event());
EXPECT_NEAR(touch_move_point_in_child.x(),
touch_move_event_received.touches[0].PositionInWidget().x,
kCoordinateTolerance);
EXPECT_NEAR(touch_move_point_in_child.y(),
touch_move_event_received.touches[0].PositionInWidget().y,
kCoordinateTolerance);
// TouchEnd.
blink::WebTouchEvent touch_end_event(
blink::WebInputEvent::kTouchEnd, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
touch_end_event.touches_length = 1;
touch_end_event.touches[0].state = blink::WebTouchPoint::kStateReleased;
SetWebEventPositions(&touch_end_event.touches[0], touch_move_point,
root_rwhv);
touch_end_event.unique_touch_event_id = 3;
router->RouteTouchEvent(root_rwhv, &touch_end_event,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
child_touch_end_waiter.Wait();
ASSERT_EQ(3U, child_event_observer.events_received().size());
ASSERT_EQ(blink::WebInputEvent::kTouchEnd,
child_event_observer.event().GetType());
const blink::WebTouchEvent& touch_end_event_received =
static_cast<const blink::WebTouchEvent&>(child_event_observer.event());
EXPECT_NEAR(touch_move_point_in_child.x(),
touch_end_event_received.touches[0].PositionInWidget().x,
kCoordinateTolerance);
EXPECT_NEAR(touch_move_point_in_child.y(),
touch_end_event_received.touches[0].PositionInWidget().y,
kCoordinateTolerance);
}
// When a scroll event is bubbled, ensure that the bubbled event's coordinates
// are correctly updated to the ancestor's coordinate space. In particular,
// ensure that the transformation considers CSS scaling of the child where
// simply applying the ancestor's offset does not produce the correct
// coordinates in the ancestor's coordinate space.
// See https://crbug.com/817392
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
BubbledScrollEventsTransformedCorrectly) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_scaled_frame.html"));
ASSERT_TRUE(NavigateToURL(shell(), main_url));
RenderFrameSubmissionObserver render_frame_submission_observer(
shell()->web_contents());
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* iframe_node = root->child_at(0);
GURL site_url(embedded_test_server()->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, iframe_node->current_url());
RenderWidgetHostViewBase* root_rwhv = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostInputEventRouter* router =
static_cast<WebContentsImpl*>(shell()->web_contents())
->GetInputEventRouter();
WaitForHitTestDataOrChildSurfaceReady(iframe_node->current_frame_host());
const float scale_factor =
render_frame_submission_observer.LastRenderFrameMetadata()
.page_scale_factor;
// Due to the CSS scaling of the iframe, the position in the child view's
// coordinates is (96, 96) and not (48, 48) (or approximately these values
// if there's rounding due to the scale factor).
const gfx::Point position_in_root(gfx::ToCeiledInt(150 * scale_factor),
gfx::ToCeiledInt(150 * scale_factor));
auto expect_gsb_with_position = base::BindRepeating(
[](const gfx::Point& expected_position, content::InputEventAckSource,
content::InputEventAckState, const blink::WebInputEvent& event) {
if (event.GetType() != blink::WebInputEvent::kGestureScrollBegin)
return false;
const blink::WebGestureEvent& gesture_event =
static_cast<const blink::WebGestureEvent&>(event);
EXPECT_NEAR(expected_position.x(), gesture_event.PositionInWidget().x,
kHitTestTolerance);
EXPECT_NEAR(expected_position.y(), gesture_event.PositionInWidget().y,
kHitTestTolerance);
return true;
});
InputEventAckWaiter root_scroll_begin_observer(
root_rwhv->GetRenderWidgetHost(),
base::BindRepeating(expect_gsb_with_position, position_in_root));
// Scroll the iframe upward, scroll events get bubbled up to the root.
blink::WebMouseWheelEvent scroll_event(
blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
SetWebEventPositions(&scroll_event, position_in_root, root_rwhv);
scroll_event.delta_x = 0.0f;
scroll_event.delta_y = 5.0f;
scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan;
scroll_event.has_precise_scrolling_deltas = true;
router->RouteMouseWheelEvent(root_rwhv, &scroll_event, ui::LatencyInfo());
root_scroll_begin_observer.Wait();
}
namespace {
// Waits until an event of the given type has been sent to the given
// RenderWidgetHost.
class OutgoingEventWaiter : public RenderWidgetHost::InputEventObserver {
public:
explicit OutgoingEventWaiter(RenderWidgetHostImpl* rwh,
blink::WebInputEvent::Type type)
: rwh_(rwh->GetWeakPtr()), type_(type) {
rwh->AddInputEventObserver(this);
}
~OutgoingEventWaiter() override {
if (rwh_)
rwh_->RemoveInputEventObserver(this);
}
void OnInputEvent(const blink::WebInputEvent& event) override {
if (event.GetType() == type_) {
seen_event_ = true;
if (quit_closure_)
std::move(quit_closure_).Run();
}
}
void Wait() {
if (!seen_event_) {
base::RunLoop run_loop;
quit_closure_ = run_loop.QuitClosure();
run_loop.Run();
}
}
private:
base::WeakPtr<RenderWidgetHostImpl> rwh_;
const blink::WebInputEvent::Type type_;
bool seen_event_ = false;
base::OnceClosure quit_closure_;
};
// Fails the test if an event of the given type is sent to the given
// RenderWidgetHost.
class BadInputEventObserver : public RenderWidgetHost::InputEventObserver {
public:
explicit BadInputEventObserver(RenderWidgetHostImpl* rwh,
blink::WebInputEvent::Type type)
: rwh_(rwh->GetWeakPtr()), type_(type) {
rwh->AddInputEventObserver(this);
}
~BadInputEventObserver() override {
if (rwh_)
rwh_->RemoveInputEventObserver(this);
}
void OnInputEvent(const blink::WebInputEvent& event) override {
EXPECT_NE(type_, event.GetType())
<< "Unexpected " << blink::WebInputEvent::GetName(event.GetType());
}
private:
base::WeakPtr<RenderWidgetHostImpl> rwh_;
const blink::WebInputEvent::Type type_;
};
} // namespace
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
ScrollBubblingTargetWithUnrelatedGesture) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_nested_frames.html"));
ASSERT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* parent_iframe_node = root->child_at(0);
ASSERT_EQ(1U, parent_iframe_node->child_count());
GURL nested_frame_url(embedded_test_server()->GetURL(
"baz.com", "/page_with_touch_start_janking_main_thread.html"));
NavigateFrameToURL(parent_iframe_node->child_at(0), nested_frame_url);
RenderWidgetHostViewBase* root_rwhv = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewChildFrame* rwhv_parent =
static_cast<RenderWidgetHostViewChildFrame*>(
parent_iframe_node->current_frame_host()
->GetRenderWidgetHost()
->GetView());
RenderWidgetHostViewChildFrame* rwhv_nested =
static_cast<RenderWidgetHostViewChildFrame*>(
parent_iframe_node->child_at(0)
->current_frame_host()
->GetRenderWidgetHost()
->GetView());
RenderWidgetHostInputEventRouter* router =
static_cast<WebContentsImpl*>(shell()->web_contents())
->GetInputEventRouter();
WaitForHitTestDataOrChildSurfaceReady(
parent_iframe_node->child_at(0)->current_frame_host());
OutgoingEventWaiter outgoing_touch_end_waiter(
static_cast<RenderWidgetHostImpl*>(rwhv_nested->GetRenderWidgetHost()),
blink::WebInputEvent::kTouchEnd);
InputEventAckWaiter scroll_end_at_parent(
rwhv_parent->GetRenderWidgetHost(),
blink::WebInputEvent::kGestureScrollEnd);
BadInputEventObserver no_scroll_bubbling_to_root(
static_cast<RenderWidgetHostImpl*>(root_rwhv->GetRenderWidgetHost()),
blink::WebInputEvent::kGestureScrollBegin);
MainThreadFrameObserver synchronize_threads(
rwhv_nested->GetRenderWidgetHost());
synchronize_threads.Wait();
#if defined(USE_AURA)
// Allow the scroll gesture through under mash.
base::Optional<SystemEventRewriter::ScopedAllow> maybe_scoped_allow_events;
if (features::IsSingleProcessMash()) {
maybe_scoped_allow_events.emplace(&event_rewriter_);
}
#endif
SyntheticSmoothScrollGestureParams params;
params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT;
const gfx::PointF location_in_widget(25, 25);
const gfx::PointF location_in_root =
rwhv_nested->TransformPointToRootCoordSpaceF(location_in_widget);
params.anchor = location_in_root;
params.distances.push_back(gfx::Vector2d(0, 100));
params.prevent_fling = false;
RenderWidgetHostImpl* root_widget_host =
static_cast<RenderWidgetHostImpl*>(root_rwhv->GetRenderWidgetHost());
auto dont_care_on_complete =
base::BindOnce([](SyntheticGesture::Result result) {});
root_widget_host->QueueSyntheticGesture(
std::make_unique<SyntheticSmoothScrollGesture>(params),
std::move(dont_care_on_complete));
outgoing_touch_end_waiter.Wait();
// We are now waiting for the touch events to be acked from the nested OOPIF
// which will result in a scroll gesture that will bubble from the nested
// frame. Meanwhile, we start a new gesture in the main frame.
const gfx::PointF point_in_root(1, 1);
blink::WebTouchEvent touch_event(
blink::WebInputEvent::kTouchStart, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
touch_event.touches_length = 1;
touch_event.touches[0].state = blink::WebTouchPoint::kStatePressed;
SetWebEventPositions(&touch_event.touches[0], point_in_root, root_rwhv);
touch_event.unique_touch_event_id = 1;
InputEventAckWaiter root_touch_waiter(root_rwhv->GetRenderWidgetHost(),
blink::WebInputEvent::kTouchStart);
router->RouteTouchEvent(root_rwhv, &touch_event,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
root_touch_waiter.Wait();
blink::WebGestureEvent gesture_event(
blink::WebInputEvent::kGestureTapDown, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests(),
blink::WebGestureDevice::kTouchscreen);
gesture_event.unique_touch_event_id = touch_event.unique_touch_event_id;
router->RouteGestureEvent(root_rwhv, &gesture_event,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
scroll_end_at_parent.Wait();
// By this point, the parent frame attempted to bubble scroll to the main
// frame. |no_scroll_bubbling_to_root| checks that the bubbling stopped at
// the parent.
}
class SitePerProcessEmulatedTouchBrowserTest
: public SitePerProcessHitTestBrowserTest {
public:
enum TestType {
ScrollBubbling,
PinchGoesToMainFrame,
TouchActionBubbling,
ShowPressHasTouchID
};
~SitePerProcessEmulatedTouchBrowserTest() override {}
void RunTest(TestType test_type) {
std::string url;
if (test_type == TouchActionBubbling)
url = "/frame_tree/page_with_pany_frame.html";
else
url = "/frame_tree/page_with_positioned_frame.html";
GURL main_url(embedded_test_server()->GetURL(url));
ASSERT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* iframe_node = root->child_at(0);
GURL site_url(embedded_test_server()->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, iframe_node->current_url());
RenderWidgetHostViewBase* root_rwhv =
static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* child_rwhv =
static_cast<RenderWidgetHostViewBase*>(iframe_node->current_frame_host()
->GetRenderWidgetHost()
->GetView());
RenderWidgetHostInputEventRouter* router =
static_cast<WebContentsImpl*>(shell()->web_contents())
->GetInputEventRouter();
WaitForHitTestDataOrChildSurfaceReady(iframe_node->current_frame_host());
auto expect_gesture_with_position = base::BindRepeating(
[](blink::WebInputEvent::Type expected_type,
const gfx::Point& expected_position, content::InputEventAckSource,
content::InputEventAckState, const blink::WebInputEvent& event) {
if (event.GetType() != expected_type)
return false;
const blink::WebGestureEvent& gesture_event =
static_cast<const blink::WebGestureEvent&>(event);
EXPECT_NEAR(expected_position.x(), gesture_event.PositionInWidget().x,
kHitTestTolerance);
EXPECT_NEAR(expected_position.y(), gesture_event.PositionInWidget().y,
kHitTestTolerance);
EXPECT_EQ(blink::WebGestureDevice::kTouchscreen,
gesture_event.SourceDevice());
// We expect all gesture events to have non-zero ids otherwise they
// can force hit-testing in RenderWidgetHostInputEventRouter even
// when it's unnecessary.
EXPECT_NE(0U, gesture_event.unique_touch_event_id);
return true;
});
blink::WebInputEvent::Type expected_gesture_type;
switch (test_type) {
case ScrollBubbling:
case TouchActionBubbling:
expected_gesture_type = blink::WebInputEvent::kGestureScrollBegin;
break;
case PinchGoesToMainFrame:
expected_gesture_type = blink::WebInputEvent::kGesturePinchBegin;
break;
case ShowPressHasTouchID:
expected_gesture_type = blink::WebInputEvent::kGestureShowPress;
break;
default:
ASSERT_TRUE(false);
}
#if defined(OS_WIN)
{
gfx::Rect view_bounds = root_rwhv->GetViewBounds();
LOG(ERROR) << "Root view bounds = (" << view_bounds.x() << ","
<< view_bounds.y() << ") " << view_bounds.width() << " x "
<< view_bounds.height();
}
#endif
gfx::Point position_in_child(5, 5);
InputEventAckWaiter child_gesture_event_observer(
child_rwhv->GetRenderWidgetHost(),
base::BindRepeating(expect_gesture_with_position, expected_gesture_type,
position_in_child));
gfx::Point position_in_root =
child_rwhv->TransformPointToRootCoordSpace(position_in_child);
InputEventAckWaiter root_gesture_event_observer(
root_rwhv->GetRenderWidgetHost(),
base::BindRepeating(expect_gesture_with_position, expected_gesture_type,
position_in_root));
// Enable touch emulation.
auto* touch_emulator = router->GetTouchEmulator();
ASSERT_TRUE(touch_emulator);
touch_emulator->Enable(TouchEmulator::Mode::kEmulatingTouchFromMouse,
ui::GestureProviderConfigType::CURRENT_PLATFORM);
// Create mouse events to emulate touch scroll. Since the page has no touch
// handlers, these events will be converted into a gesture scroll sequence.
base::TimeTicks simulated_event_time = ui::EventTimeForNow();
base::TimeDelta simulated_event_time_delta =
base::TimeDelta::FromMilliseconds(100);
blink::WebMouseEvent mouse_move_event =
SyntheticWebMouseEventBuilder::Build(blink::WebInputEvent::kMouseMove,
position_in_root.x(),
position_in_root.y(), 0);
mouse_move_event.SetTimeStamp(simulated_event_time);
int mouse_modifier = (test_type == PinchGoesToMainFrame)
? blink::WebInputEvent::kShiftKey
: 0;
mouse_modifier |= blink::WebInputEvent::kLeftButtonDown;
blink::WebMouseEvent mouse_down_event =
SyntheticWebMouseEventBuilder::Build(
blink::WebInputEvent::kMouseDown, position_in_root.x(),
position_in_root.y(), mouse_modifier);
mouse_down_event.button = blink::WebMouseEvent::Button::kLeft;
simulated_event_time += simulated_event_time_delta;
mouse_down_event.SetTimeStamp(simulated_event_time);
blink::WebMouseEvent mouse_drag_event =
SyntheticWebMouseEventBuilder::Build(
blink::WebInputEvent::kMouseMove, position_in_root.x(),
position_in_root.y() + 20, mouse_modifier);
simulated_event_time += simulated_event_time_delta;
mouse_drag_event.SetTimeStamp(simulated_event_time);
mouse_drag_event.button = blink::WebMouseEvent::Button::kLeft;
blink::WebMouseEvent mouse_up_event = SyntheticWebMouseEventBuilder::Build(
blink::WebInputEvent::kMouseUp, position_in_root.x(),
position_in_root.y() + 20, mouse_modifier);
mouse_up_event.button = blink::WebMouseEvent::Button::kLeft;
simulated_event_time += simulated_event_time_delta;
mouse_up_event.SetTimeStamp(simulated_event_time);
// Send mouse events and wait for GesturePinchBegin.
router->RouteMouseEvent(root_rwhv, &mouse_move_event, ui::LatencyInfo());
router->RouteMouseEvent(root_rwhv, &mouse_down_event, ui::LatencyInfo());
if (test_type == ShowPressHasTouchID) {
// Wait for child to receive GestureShowPress. If this test fails, it
// will either DCHECK or time out.
child_gesture_event_observer.Wait();
return;
}
router->RouteMouseEvent(root_rwhv, &mouse_drag_event, ui::LatencyInfo());
router->RouteMouseEvent(root_rwhv, &mouse_up_event, ui::LatencyInfo());
if (test_type == ScrollBubbling || test_type == TouchActionBubbling) {
// Verify child receives GestureScrollBegin.
child_gesture_event_observer.Wait();
}
// Verify the root receives the GesturePinchBegin or GestureScrollBegin,
// depending on |test_type|.
root_gesture_event_observer.Wait();
// Shut down.
touch_emulator->Disable();
}
};
IN_PROC_BROWSER_TEST_P(SitePerProcessEmulatedTouchBrowserTest,
EmulatedTouchShowPressHasTouchID) {
RunTest(ShowPressHasTouchID);
}
IN_PROC_BROWSER_TEST_P(SitePerProcessEmulatedTouchBrowserTest,
EmulatedTouchScrollBubbles) {
RunTest(ScrollBubbling);
}
IN_PROC_BROWSER_TEST_P(SitePerProcessEmulatedTouchBrowserTest,
EmulatedTouchPinchGoesToMainFrame) {
RunTest(PinchGoesToMainFrame);
}
IN_PROC_BROWSER_TEST_P(SitePerProcessEmulatedTouchBrowserTest,
EmulatedGestureScrollBubbles) {
RunTest(TouchActionBubbling);
}
// Regression test for https://crbug.com/851644. The test passes as long as it
// doesn't crash.
// Touch action ack timeout is enabled on Android only.
#if defined(OS_ANDROID)
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
TouchActionAckTimeout) {
GURL main_url(
embedded_test_server()->GetURL("/frame_tree/page_with_janky_frame.html"));
ASSERT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
GURL frame_url(embedded_test_server()->GetURL(
"baz.com", "/page_with_touch_start_janking_main_thread.html"));
auto* child_frame_host = root->child_at(0)->current_frame_host();
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewChildFrame* rwhv_child =
static_cast<RenderWidgetHostViewChildFrame*>(
child_frame_host->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_frame_host);
// Compute the point so that the gesture event can target the child frame.
const gfx::Rect root_bounds = rwhv_root->GetViewBounds();
const gfx::Rect child_bounds = rwhv_child->GetViewBounds();
RenderFrameSubmissionObserver render_frame_submission_observer(
shell()->web_contents());
const float page_scale_factor =
render_frame_submission_observer.LastRenderFrameMetadata()
.page_scale_factor;
const gfx::PointF point_in_child(
(child_bounds.x() - root_bounds.x() + 25) * page_scale_factor,
(child_bounds.y() - root_bounds.y() + 25) * page_scale_factor);
SyntheticSmoothScrollGestureParams params;
params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT;
params.anchor = gfx::PointF(point_in_child.x(), point_in_child.y());
params.distances.push_back(gfx::Vector2dF(0, -10));
// The JS jank from the "page_with_touch_start_janking_main_thread.html"
// causes the touch ack timeout. Set the speed high so that the gesture can be
// completed quickly and so does this test.
params.speed_in_pixels_s = 100000;
std::unique_ptr<SyntheticSmoothScrollGesture> gesture(
new SyntheticSmoothScrollGesture(params));
InputEventAckWaiter ack_observer(
child_frame_host->GetRenderWidgetHost(),
base::BindRepeating([](content::InputEventAckSource source,
content::InputEventAckState state,
const blink::WebInputEvent& event) {
return event.GetType() == blink::WebGestureEvent::kGestureScrollEnd;
}));
ack_observer.Reset();
RenderWidgetHostImpl* render_widget_host =
root->current_frame_host()->GetRenderWidgetHost();
render_widget_host->QueueSyntheticGesture(
std::move(gesture), base::BindOnce([](SyntheticGesture::Result result) {
EXPECT_EQ(SyntheticGesture::GESTURE_FINISHED, result);
}));
ack_observer.Wait();
}
#endif // defined(OS_ANDROID)
#if defined(USE_AURA) || defined(OS_ANDROID)
// When unconsumed scrolls in a child bubble to the root and start an
// overscroll gesture, the subsequent gesture scroll update events should be
// consumed by the root. The child should not be able to scroll during the
// overscroll gesture.
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
RootConsumesScrollDuringOverscrollGesture) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
RenderFrameSubmissionObserver render_frame_submission_observer(
shell()->web_contents());
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
#if defined(USE_AURA)
// The child must be horizontally scrollable.
GURL child_url(embedded_test_server()->GetURL("b.com", "/wide_page.html"));
#elif defined(OS_ANDROID)
// The child must be vertically scrollable.
GURL child_url(embedded_test_server()->GetURL("b.com", "/tall_page.html"));
#endif
NavigateFrameToURL(child_node, child_url);
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://b.com/",
DepictFrameTree(root));
RenderWidgetHostViewChildFrame* rwhv_child =
static_cast<RenderWidgetHostViewChildFrame*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
ASSERT_TRUE(rwhv_root->IsScrollOffsetAtTop());
ASSERT_TRUE(rwhv_child->IsScrollOffsetAtTop());
RenderWidgetHostInputEventRouter* router =
static_cast<WebContentsImpl*>(shell()->web_contents())
->GetInputEventRouter();
{
// Set up the RenderWidgetHostInputEventRouter to send the gesture stream
// to the child.
const gfx::Rect root_bounds = rwhv_root->GetViewBounds();
const gfx::Rect child_bounds = rwhv_child->GetViewBounds();
const float page_scale_factor =
render_frame_submission_observer.LastRenderFrameMetadata()
.page_scale_factor;
const gfx::PointF point_in_root(
(child_bounds.x() - root_bounds.x() + 10) * page_scale_factor,
(child_bounds.y() - root_bounds.y() + 10) * page_scale_factor);
blink::WebTouchEvent touch_event(
blink::WebInputEvent::kTouchStart, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
touch_event.touches_length = 1;
touch_event.touches[0].state = blink::WebTouchPoint::kStatePressed;
SetWebEventPositions(&touch_event.touches[0], point_in_root, rwhv_root);
touch_event.unique_touch_event_id = 1;
InputEventAckWaiter waiter(rwhv_child->GetRenderWidgetHost(),
blink::WebInputEvent::kTouchStart);
router->RouteTouchEvent(rwhv_root, &touch_event,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
// With async hit testing, make sure the target for the initial TouchStart
// is resolved before sending the rest of the stream.
waiter.Wait();
blink::WebGestureEvent gesture_event(
blink::WebInputEvent::kGestureTapDown,
blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests(),
blink::WebGestureDevice::kTouchscreen);
gesture_event.unique_touch_event_id = touch_event.unique_touch_event_id;
router->RouteGestureEvent(rwhv_root, &gesture_event,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
}
#if defined(USE_AURA)
RenderWidgetHostViewAura* rwhva =
static_cast<RenderWidgetHostViewAura*>(rwhv_root);
std::unique_ptr<MockOverscrollControllerDelegateAura>
mock_overscroll_delegate =
std::make_unique<MockOverscrollControllerDelegateAura>(rwhva);
rwhva->overscroll_controller()->set_delegate(mock_overscroll_delegate.get());
MockOverscrollObserver* mock_overscroll_observer =
mock_overscroll_delegate.get();
#elif defined(OS_ANDROID)
RenderWidgetHostViewAndroid* rwhv_android =
static_cast<RenderWidgetHostViewAndroid*>(rwhv_root);
std::unique_ptr<MockOverscrollRefreshHandlerAndroid> mock_overscroll_handler =
std::make_unique<MockOverscrollRefreshHandlerAndroid>();
rwhv_android->SetOverscrollControllerForTesting(
mock_overscroll_handler.get());
MockOverscrollObserver* mock_overscroll_observer =
mock_overscroll_handler.get();
#endif // defined(USE_AURA)
InputEventAckWaiter gesture_begin_observer_child(
child_node->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kGestureScrollBegin);
InputEventAckWaiter gesture_end_observer_child(
child_node->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kGestureScrollEnd);
#if defined(USE_AURA)
const float overscroll_threshold = OverscrollConfig::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::WebGestureDevice::kTouchscreen);
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::WebGestureDevice::kTouchscreen);
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::WebGestureDevice::kTouchscreen);
gesture_scroll_end.unique_touch_event_id = 1;
gesture_scroll_end.data.scroll_end.delta_units =
blink::WebGestureEvent::ScrollUnits::kPrecisePixels;
mock_overscroll_observer->Reset();
router->RouteGestureEvent(rwhv_root, &gesture_scroll_end,
ui::LatencyInfo(ui::SourceEventType::TOUCH));
mock_overscroll_observer->WaitForEnd();
// Ensure that the method of providing the child's scroll events to the root
// does not leave the child in an invalid state.
gesture_end_observer_child.Wait();
}
#endif // defined(USE_AURA) || defined(OS_ANDROID)
// Test that an ET_SCROLL event sent to an out-of-process iframe correctly
// results in a scroll. This is only handled by RenderWidgetHostViewAura
// and is needed for trackpad scrolling on Chromebooks.
#if defined(USE_AURA)
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, ScrollEventToOOPIF) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server()->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewAura* rwhv_parent =
static_cast<RenderWidgetHostViewAura*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
// Create listener for input events.
TestInputEventObserver child_frame_monitor(
child_node->current_frame_host()->GetRenderWidgetHost());
// Send a ui::ScrollEvent that will hit test to the child frame.
InputEventAckWaiter waiter(
child_node->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kMouseWheel);
ui::ScrollEvent scroll_event(ui::ET_SCROLL, gfx::Point(75, 75),
ui::EventTimeForNow(), ui::EF_NONE, 0,
10, // Offsets
0, 10, // Offset ordinals
2);
UpdateEventRootLocation(&scroll_event, rwhv_parent);
rwhv_parent->OnScrollEvent(&scroll_event);
waiter.Wait();
// Verify that this a mouse wheel event was sent to the child frame renderer.
EXPECT_TRUE(child_frame_monitor.EventWasReceived());
EXPECT_TRUE(base::ContainsValue(child_frame_monitor.events_received(),
blink::WebInputEvent::kMouseWheel));
}
// Tests that touching an OOPIF editable element correctly resizes the
// viewport and scrolls the element into view so that the element is not
// occluded by the on screen keyboard (https://crbug.com/927483)
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
ScrollOOPIFEditableElement) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/oopif_form_scroll_main.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
// Get the first iframe, which contains the editable element
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server()->GetURL(
"baz.com", "/frame_tree/page_with_editable_elements.html"));
ASSERT_EQ(site_url, child_node->current_url());
// Make sure the child frame is indeed a OOPIF
EXPECT_TRUE(child_node->current_frame_host()->IsCrossProcessSubframe());
// Set focus on an element in the OOPIF
EXPECT_TRUE(ExecJs(child_node->current_frame_host(),
"document.getElementById('oopif_element').focus()"));
MainThreadFrameObserver observer(
root->current_frame_host()->GetRenderWidgetHost());
observer.Wait();
// We reset the scroll to 0,0 because setting focus on element
// will bring it into view
ASSERT_TRUE(ExecJs(root->current_frame_host(),
base::StringPrintf("window.scrollTo(%d, %d);", 0, 0)));
content::RenderFrameSubmissionObserver root_frame_observer(root);
gfx::Vector2dF zero_offset;
root_frame_observer.WaitForScrollOffset(zero_offset);
RenderWidgetHostViewChildFrame* child_render_widget_host_view_child_frame =
static_cast<RenderWidgetHostViewChildFrame*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewAura* parent_render_widget_host_aura =
static_cast<RenderWidgetHostViewAura*>(
child_render_widget_host_view_child_frame->GetRootView());
gfx::Size original_viewport_size =
parent_render_widget_host_aura->GetVisibleViewportSize();
int htmlScrollHeight = EvalJs(root->current_frame_host(),
"document.documentElement.scrollHeight")
.ExtractInt();
const int inset_height = 200;
parent_render_widget_host_aura->SetLastPointerType(
ui::EventPointerType::POINTER_TYPE_TOUCH);
parent_render_widget_host_aura->FocusedNodeTouched(true);
parent_render_widget_host_aura->SetInsets(gfx::Insets(0, 0, inset_height, 0));
// After focus on editable element, we expect element to be scrolled
// into view. Verify that the scroll offset on the root document
// matches a value that would make the OOPIF element visible.
// When a page is scrolled to the bottom
// window.scrollY == htmlScrollHeight - viewportHeight;
// Verify that setting insets scrolled to the bottom to make
// editable element visible.
gfx::Vector2dF final_root_offset(
0, htmlScrollHeight - (original_viewport_size.height() - inset_height));
root_frame_observer.WaitForScrollOffset(final_root_offset);
EXPECT_EQ(EvalJs(root->current_frame_host(), "window.scrollY").ExtractInt(),
htmlScrollHeight - original_viewport_size.height());
// Viewport should be resized by keyboard height
EXPECT_NE(parent_render_widget_host_aura->GetVisibleViewportSize(),
original_viewport_size);
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
InputEventRouterWheelCoalesceTest) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server()->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewAura* rwhv_parent =
static_cast<RenderWidgetHostViewAura*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
RenderWidgetHostInputEventRouter* router =
web_contents()->GetInputEventRouter();
// Create listener for input events.
TestInputEventObserver child_frame_monitor(
child_node->current_frame_host()->GetRenderWidgetHost());
InputEventAckWaiter waiter(
child_node->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kMouseWheel);
// Send a mouse wheel event to child.
blink::WebMouseWheelEvent wheel_event(
blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
SetWebEventPositions(&wheel_event, gfx::Point(75, 75), rwhv_parent);
wheel_event.delta_x = 10;
wheel_event.delta_y = 20;
wheel_event.phase = blink::WebMouseWheelEvent::kPhaseBegan;
router->RouteMouseWheelEvent(rwhv_parent, &wheel_event, ui::LatencyInfo());
// Send more mouse wheel events to the child. Since we are waiting for the
// async targeting on the first event, these new mouse wheel events should
// be coalesced properly.
blink::WebMouseWheelEvent wheel_event1(
blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
SetWebEventPositions(&wheel_event1, gfx::Point(70, 70), rwhv_parent);
wheel_event1.delta_x = 12;
wheel_event1.delta_y = 22;
wheel_event1.phase = blink::WebMouseWheelEvent::kPhaseChanged;
router->RouteMouseWheelEvent(rwhv_parent, &wheel_event1, ui::LatencyInfo());
blink::WebMouseWheelEvent wheel_event2(
blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
SetWebEventPositions(&wheel_event2, gfx::Point(65, 65), rwhv_parent);
wheel_event2.delta_x = 14;
wheel_event2.delta_y = 24;
wheel_event2.phase = blink::WebMouseWheelEvent::kPhaseChanged;
router->RouteMouseWheelEvent(rwhv_parent, &wheel_event2, ui::LatencyInfo());
// Since we are targeting child, event dispatch should not happen
// synchronously. Validate that the expected target does not receive the
// event immediately.
// When V2 surface layer hit testing is enabled, we expect to do synchronous
// event targeting on a child under some circumstances, so we expect the event
// immediately dispatched to the child.
if (!features::IsVizHitTestingSurfaceLayerEnabled())
EXPECT_FALSE(child_frame_monitor.EventWasReceived());
waiter.Wait();
EXPECT_TRUE(child_frame_monitor.EventWasReceived());
EXPECT_EQ(child_frame_monitor.EventType(), blink::WebInputEvent::kMouseWheel);
// Check if the two mouse-wheel update events are coalesced correctly.
const auto& gesture_event =
static_cast<const blink::WebGestureEvent&>(child_frame_monitor.event());
EXPECT_EQ(26 /* wheel_event1.delta_x + wheel_event2.delta_x */,
gesture_event.data.scroll_update.delta_x);
EXPECT_EQ(46 /* wheel_event1.delta_y + wheel_event2.delta_y */,
gesture_event.data.scroll_update.delta_y);
}
#endif // defined(USE_AURA)
// Test that mouse events are being routed to the correct RenderWidgetHostView
// based on coordinates.
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, SurfaceHitTestTest) {
SurfaceHitTestTestHelper(shell(), embedded_test_server());
}
// Same test as above, but runs in high-dpi mode.
// NOTE: This has to be renamed from SurfaceHitTestTest to
// HighDPISurfaceHitTestTest. Otherwise MAYBE_SurfaceHitTestTest gets #defined
// twice.
#if defined(OS_ANDROID) || defined(OS_WIN)
// High DPI browser tests are not needed on Android, and confuse some of the
// coordinate calculations. Android uses fixed device scale factor.
// Windows is disabled because of https://crbug.com/545547.
#define MAYBE_HighDPISurfaceHitTestTest DISABLED_HighDPISurfaceHitTestTest
#else
#define MAYBE_HighDPISurfaceHitTestTest HighDPISurfaceHitTestTest
#endif
IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest,
MAYBE_HighDPISurfaceHitTestTest) {
SurfaceHitTestTestHelper(shell(), embedded_test_server());
}
// TODO(https://crbug.com/948372): tests are flaky on ChromeOS and often
// timeout.
#if defined(OS_CHROMEOS)
#define MAYBE_NestedSurfaceHitTestTest DISABLED_NestedSurfaceHitTestTest
#else
#define MAYBE_NestedSurfaceHitTestTest NestedSurfaceHitTestTest
#endif
// Test that mouse events are being routed to the correct RenderWidgetHostView
// when there are nested out-of-process iframes.
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
MAYBE_NestedSurfaceHitTestTest) {
NestedSurfaceHitTestTestHelper(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest,
MAYBE_NestedSurfaceHitTestTest) {
NestedSurfaceHitTestTestHelper(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
NonFlatTransformedSurfaceHitTestTest) {
NonFlatTransformedSurfaceHitTestHelper(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest,
NonFlatTransformedSurfaceHitTestTest) {
NonFlatTransformedSurfaceHitTestHelper(shell(), embedded_test_server());
}
// TODO(kenrb): Running this test on Android bots has slight discrepancies in
// transformed event coordinates when we do manual calculation of expected
// values. We can't rely on browser side transformation because it is broken
// for perspective transforms. See https://crbug.com/854247.
#if defined(OS_ANDROID)
#define MAYBE_PerspectiveTransformedSurfaceHitTestTest \
DISABLED_PerspectiveTransformedSurfaceHitTestTest
#else
#define MAYBE_PerspectiveTransformedSurfaceHitTestTest \
PerspectiveTransformedSurfaceHitTestTest
#endif
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
MAYBE_PerspectiveTransformedSurfaceHitTestTest) {
PerspectiveTransformedSurfaceHitTestHelper(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest,
MAYBE_PerspectiveTransformedSurfaceHitTestTest) {
PerspectiveTransformedSurfaceHitTestHelper(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest,
OverlapSurfaceHitTestTest) {
OverlapSurfaceHitTestHelper(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
OverlapSurfaceHitTestTest) {
OverlapSurfaceHitTestHelper(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
HitTestLayerSquashing) {
HitTestLayerSquashing(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest,
HitTestLayerSquashing) {
HitTestLayerSquashing(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, HitTestWatermark) {
HitTestWatermark(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest,
HitTestWatermark) {
HitTestWatermark(shell(), embedded_test_server());
}
#if defined(USE_AURA)
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, RootWindowTransform) {
HitTestRootWindowTransform(shell(), embedded_test_server());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest,
RootWindowTransform) {
HitTestRootWindowTransform(shell(), embedded_test_server());
}
#endif // defined(USE_AURA)
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
HitTestStaleDataDeletedView) {
// Have two iframes to avoid going to short circuit path during the second
// targeting.
GURL main_url(
embedded_test_server()->GetURL("/frame_tree/page_with_two_iframes.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
auto* web_contents = static_cast<WebContentsImpl*>(shell()->web_contents());
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(2U, root->child_count());
FrameTreeNode* child_node1 = root->child_at(0);
GURL site_url1(embedded_test_server()->GetURL("bar.com", "/title1.html"));
EXPECT_EQ(site_url1, child_node1->current_url());
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
child_node1->current_frame_host()->GetSiteInstance());
FrameTreeNode* child_node2 = root->child_at(1);
GURL site_url2(embedded_test_server()->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url2, child_node2->current_url());
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
child_node2->current_frame_host()->GetSiteInstance());
RenderWidgetHostImpl* root_rwh = static_cast<RenderWidgetHostImpl*>(
root->current_frame_host()->GetRenderWidgetHost());
RenderWidgetHostViewBase* rwhv_parent =
static_cast<RenderWidgetHostViewBase*>(root_rwh->GetView());
RenderWidgetHostViewBase* rwhv_child2 =
static_cast<RenderWidgetHostViewBase*>(
child_node2->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node1->current_frame_host());
WaitForHitTestDataOrChildSurfaceReady(child_node2->current_frame_host());
const gfx::PointF child_location(50, 50);
gfx::PointF parent_location =
rwhv_child2->TransformPointToRootCoordSpaceF(child_location);
// Send a mouse-down at the center of the child2. This should go to the
// child2.
DispatchMouseEventAndWaitUntilDispatch(
web_contents, rwhv_parent, parent_location, rwhv_child2, child_location);
// Remove the iframe from the page. Add an infinite loop at the end so that
// renderer wouldn't submit updated hit-test data.
FrameDeletedObserver delete_observer(child_node2->current_frame_host());
ExecuteScriptAsync(
root,
"document.body.removeChild(document.getElementsByName('frame2')[0]);"
"while(true) {}");
delete_observer.Wait();
EXPECT_EQ(1U, root->child_count());
// The synchronous targeting for the same location should now find the
// root-view as the target (and require async-targeting), since child2 has
// been removed. We cannot actually attempt to dispatch the event though,
// since it would try to do asynchronous targeting by asking the root-view,
// whose main-thread is blocked because of the infinite-loop in the injected
// javascript above.
blink::WebMouseEvent down_event(
blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
down_event.button = blink::WebPointerProperties::Button::kLeft;
down_event.click_count = 1;
SetWebEventPositions(&down_event, parent_location, rwhv_parent);
auto result = web_contents->GetInputEventRouter()->FindTargetSynchronously(
rwhv_parent, down_event);
EXPECT_EQ(result.view, rwhv_parent);
// When VizHitTestSurfaceLayer is enabled and there is only one child frame,
// we can find the target frame and are sure there are no other possible
// targets, in this case, we dispatch the event immediately without
// asynchronously querying the root-view.
if (features::IsVizHitTestingSurfaceLayerEnabled())
EXPECT_FALSE(result.should_query_view);
else
EXPECT_TRUE(result.should_query_view);
EXPECT_EQ(result.target_location.value(), parent_location);
}
// This test tests that browser process hittesting ignores frames with
// pointer-events: none.
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
SurfaceHitTestPointerEventsNoneChanged) {
// In /2 hit testing, OOPIFs with pointer-events: none are ignored and no hit
// test data is submitted. To make sure we wait enough time until child frame
// fully loaded, we add a 1x1 pixel OOPIF for the test to track the process of
// /2 hit testing.
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_frame_pointer-events_none.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(2U, root->child_count());
FrameTreeNode* child_node1 = root->child_at(0);
FrameTreeNode* child_node2 = root->child_at(1);
GURL site_url(embedded_test_server()->GetURL("bar.com", "/title1.html"));
EXPECT_EQ(site_url, child_node2->current_url());
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
child_node2->current_frame_host()->GetSiteInstance());
// Create listeners for mouse events.
RenderWidgetHostMouseEventMonitor main_frame_monitor(
root->current_frame_host()->GetRenderWidgetHost());
RenderWidgetHostMouseEventMonitor child_frame_monitor(
child_node1->current_frame_host()->GetRenderWidgetHost());
RenderWidgetHostInputEventRouter* router =
web_contents()->GetInputEventRouter();
RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node2->current_frame_host());
// Target input event to child1 frame.
blink::WebMouseEvent child_event(
blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
child_event.button = blink::WebPointerProperties::Button::kLeft;
SetWebEventPositions(&child_event, gfx::Point(75, 75), root_view);
child_event.click_count = 1;
main_frame_monitor.ResetEventReceived();
child_frame_monitor.ResetEventReceived();
InputEventAckWaiter waiter(root->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kMouseDown);
router->RouteMouseEvent(root_view, &child_event, ui::LatencyInfo());
waiter.Wait();
EXPECT_TRUE(main_frame_monitor.EventWasReceived());
EXPECT_NEAR(75, main_frame_monitor.event().PositionInWidget().x,
kHitTestTolerance);
EXPECT_NEAR(75, main_frame_monitor.event().PositionInWidget().y,
kHitTestTolerance);
EXPECT_FALSE(child_frame_monitor.EventWasReceived());
// Surface hit test can only learn about pointer-events changes when
// submitting compositing frame, so we disable the second half of the test for
// surface hit test.
if (!features::IsVizHitTestingEnabled())
return;
// Remove pointer-events: none property from iframe, also remove child2 to
// properly notify the observer the update.
// Wait for the confirmation of the deletion so that surface hit test is aware
// of the change of pointer-events property. When viz hit testing is enabled,
// we do not need to wait.
EXPECT_TRUE(ExecuteScript(web_contents(),
"document.getElementsByTagName('iframe')[0].style."
"pointerEvents = 'auto';\n"));
ASSERT_EQ(2U, root->child_count());
{
MainThreadFrameObserver observer(
root->current_frame_host()->GetRenderWidgetHost());
observer.Wait();
}
{
MainThreadFrameObserver observer(
root->child_at(0)->current_frame_host()->GetRenderWidgetHost());
observer.Wait();
}
{
MainThreadFrameObserver observer(
root->child_at(1)->current_frame_host()->GetRenderWidgetHost());
observer.Wait();
}
WaitForHitTestDataOrChildSurfaceReady(child_node1->current_frame_host());
WaitForHitTestDataOrChildSurfaceReady(child_node2->current_frame_host());
main_frame_monitor.ResetEventReceived();
child_frame_monitor.ResetEventReceived();
InputEventAckWaiter child_waiter(
child_node1->current_frame_host()->GetRenderWidgetHost(),
blink::WebInputEvent::kMouseDown);
router->RouteMouseEvent(root_view, &child_event, ui::LatencyInfo());
child_waiter.Wait();
EXPECT_TRUE(child_frame_monitor.EventWasReceived());
EXPECT_NEAR(23, child_frame_monitor.event().PositionInWidget().x,
kHitTestTolerance);
EXPECT_NEAR(23, child_frame_monitor.event().PositionInWidget().y,
kHitTestTolerance);
}
// This test tests that browser process can successfully hit test on nested
// OOPIFs that are partially occluded by main frame elements.
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
HitTestNestedOccludedOOPIF) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_nested_frames_and_occluding_div.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell()->web_contents());
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* parent = root->child_at(0);
GURL site_url(embedded_test_server()->GetURL(
"bar.com", "/frame_tree/page_with_positioned_frame.html"));
EXPECT_EQ(site_url, parent->current_url());
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
parent->current_frame_host()->GetSiteInstance());
ASSERT_EQ(1U, parent->child_count());
FrameTreeNode* child = parent->child_at(0);
GURL child_site_url(
embedded_test_server()->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(child_site_url, child->current_url());
RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* child_view = static_cast<RenderWidgetHostViewBase*>(
child->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child->current_frame_host());
// Target input event to the overlapping region of main frame's div and child
// frame.
DispatchMouseEventAndWaitUntilDispatch(web_contents, root_view,
gfx::PointF(75, 75), root_view,
gfx::PointF(75, 75));
// Target input event to the non overlapping region of child frame.
// The div has a bound of (0, 0, 100, 100) with a border-radius of 5px, so
// point (99, 99) should not hit test the div but reach the nested child
// frame.
// The parent frame and child frame both have a default offset of (2, 2) and
// child frame's top and left properties are set to be (50, 50), so there is
// an offset of (54, 54) in total.
DispatchMouseEventAndWaitUntilDispatch(web_contents, root_view,
gfx::PointF(99, 99), child_view,
gfx::PointF(45, 45));
}
// Verify that an event is properly retargeted to the main frame when an
// asynchronous hit test to the child frame times out.
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
AsynchronousHitTestChildTimeout) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_busy_frame.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
// Create listeners for mouse events.
RenderWidgetHostMouseEventMonitor main_frame_monitor(
root->current_frame_host()->GetRenderWidgetHost());
RenderWidgetHostMouseEventMonitor child_frame_monitor(
child_node->current_frame_host()->GetRenderWidgetHost());
EXPECT_EQ(
" Site A ------------ proxies for B C\n"
" +--Site B ------- proxies for A C\n"
" +--Site C -- proxies for A B\n"
"Where A = http://127.0.0.1/\n"
" B = http://baz.com/\n"
" C = http://bar.com/",
DepictFrameTree(root));
RenderWidgetHostInputEventRouter* router =
web_contents()->GetInputEventRouter();
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
// Shorten the timeout for purposes of this test.
router->GetRenderWidgetTargeterForTests()
->set_async_hit_test_timeout_delay_for_testing(base::TimeDelta());
RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
EXPECT_TRUE(ExecuteScript(child_node, "lookBusy();"));
// Target input event to child frame. It should get delivered to the main
// frame instead because the child frame main thread is non-responsive.
blink::WebMouseEvent child_event(
blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
child_event.button = blink::WebPointerProperties::Button::kLeft;
SetWebEventPositions(&child_event, gfx::Point(75, 75), root_view);
child_event.click_count = 1;
main_frame_monitor.ResetEventReceived();
child_frame_monitor.ResetEventReceived();
RouteMouseEventAndWaitUntilDispatch(router, root_view, root_view,
&child_event);
EXPECT_TRUE(main_frame_monitor.EventWasReceived());
EXPECT_NEAR(75, main_frame_monitor.event().PositionInWidget().x,
kHitTestTolerance);
EXPECT_NEAR(75, main_frame_monitor.event().PositionInWidget().y,
kHitTestTolerance);
EXPECT_FALSE(child_frame_monitor.EventWasReceived());
}
// Tooltips aren't used on Android, so no need to compile/run this test in that
// case.
#if !defined(OS_ANDROID)
class TooltipMonitor : public CursorManager::TooltipObserver {
public:
TooltipMonitor(CursorManager* cursor_manager) : run_loop_(new base::RunLoop) {
DCHECK(cursor_manager);
cursor_manager->SetTooltipObserverForTesting(this);
}
~TooltipMonitor() override {}
void Reset() {
run_loop_.reset(new base::RunLoop);
tooltips_received_.clear();
}
void OnSetTooltipTextForView(const RenderWidgetHostViewBase* view,
const base::string16& tooltip_text) override {
tooltips_received_.push_back(tooltip_text);
if (tooltip_text == tooltip_text_wanted_ && run_loop_->running())
run_loop_->Quit();
}
void WaitUntil(const base::string16& tooltip_text) {
tooltip_text_wanted_ = tooltip_text;
if (base::ContainsValue(tooltips_received_, tooltip_text))
return;
run_loop_->Run();
}
private:
std::unique_ptr<base::RunLoop> run_loop_;
base::string16 tooltip_text_wanted_;
std::vector<base::string16> tooltips_received_;
DISALLOW_COPY_AND_ASSIGN(TooltipMonitor);
}; // class TooltipMonitor
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
CrossProcessTooltipTest) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://b.com/",
DepictFrameTree(root));
FrameTreeNode* b_node = root->child_at(0);
RenderWidgetHostViewBase* rwhv_a = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_b = static_cast<RenderWidgetHostViewBase*>(
b_node->current_frame_host()->GetRenderWidgetHost()->GetView());
DCHECK(rwhv_a->GetCursorManager());
TooltipMonitor tooltip_monitor(rwhv_a->GetCursorManager());
WaitForHitTestDataOrChildSurfaceReady(b_node->current_frame_host());
// Make sure the point_in_a_frame value is outside the default 8px margin
// for the body element.
gfx::Point point_in_a_frame(10, 10);
gfx::Point point_in_b_frame =
rwhv_b->TransformPointToRootCoordSpace(gfx::Point(25, 25));
// Create listeners for mouse events. These are used to verify that the
// RenderWidgetHostInputEventRouter is generating MouseLeave, etc for
// the right renderers.
RenderWidgetHostMouseEventMonitor a_frame_monitor(
root->current_frame_host()->GetRenderWidgetHost());
RenderWidgetHostMouseEventMonitor b_frame_monitor(
b_node->current_frame_host()->GetRenderWidgetHost());
// Add tooltip text to both the body and the iframe in A.
std::string script =
"body = document.body.setAttribute('title', 'body_tooltip');\n"
"iframe = document.getElementsByTagName('iframe')[0];\n"
"iframe.setAttribute('title','iframe_for_b');";
EXPECT_TRUE(ExecuteScript(root->current_frame_host(), script));
// Send mouse events to both A and B.
blink::WebMouseEvent mouse_event(
blink::WebInputEvent::kMouseMove, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
auto* router = web_contents()->GetInputEventRouter();
// Alternate mouse moves between main frame and the cross-process iframe to
// test that the tool tip in the iframe can override the one set by the main
// frame renderer, even on a second entry into the iframe.
gfx::Point current_point;
for (int iteration = 0; iteration < 2; ++iteration) {
// The following is a bit of a hack to prevent hitting the same
// position/node check in ChromeClient::SetToolTip().
current_point = point_in_a_frame;
current_point.Offset(iteration, iteration);
SetWebEventPositions(&mouse_event, current_point, rwhv_a);
RouteMouseEventAndWaitUntilDispatch(router, rwhv_a, rwhv_a, &mouse_event);
EXPECT_TRUE(a_frame_monitor.EventWasReceived());
a_frame_monitor.ResetEventReceived();
// B will receive a mouseLeave on all but the first iteration.
EXPECT_EQ(iteration != 0, b_frame_monitor.EventWasReceived());
b_frame_monitor.ResetEventReceived();
tooltip_monitor.WaitUntil(base::UTF8ToUTF16("body_tooltip"));
tooltip_monitor.Reset();
// Next send a MouseMove to B frame, and A should receive a MouseMove event.
current_point = point_in_b_frame;
current_point.Offset(iteration, iteration);
SetWebEventPositions(&mouse_event, current_point, rwhv_a);
RouteMouseEventAndWaitUntilDispatch(router, rwhv_a, rwhv_b, &mouse_event);
EXPECT_TRUE(a_frame_monitor.EventWasReceived());
EXPECT_EQ(a_frame_monitor.event().GetType(),
blink::WebInputEvent::kMouseMove);
a_frame_monitor.ResetEventReceived();
EXPECT_TRUE(b_frame_monitor.EventWasReceived());
b_frame_monitor.ResetEventReceived();
tooltip_monitor.WaitUntil(base::string16());
tooltip_monitor.Reset();
}
rwhv_a->GetCursorManager()->SetTooltipObserverForTesting(nullptr);
}
#endif // !defined(OS_ANDROID)
#if defined(OS_ANDROID)
// The following test ensures that we don't get a crash if a tooltip is
// triggered on Android. This test is nearly identical to
// SitePerProcessHitTestBrowserTest.CrossProcessTooltipTestAndroid, except
// it omits the tooltip monitor, and all dereferences of GetCursorManager().
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
CrossProcessTooltipTestAndroid) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://b.com/",
DepictFrameTree(root));
FrameTreeNode* b_node = root->child_at(0);
RenderWidgetHostViewBase* rwhv_a = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_b = static_cast<RenderWidgetHostViewBase*>(
b_node->current_frame_host()->GetRenderWidgetHost()->GetView());
// On Android we don't expect GetCursorManager() to return anything other
// than nullptr. If it did, this test would be unnecessary.
DCHECK(!rwhv_a->GetCursorManager());
WaitForHitTestDataOrChildSurfaceReady(b_node->current_frame_host());
// Make sure the point_in_a_frame value is outside the default 8px margin
// for the body element.
gfx::Point point_in_a_frame(10, 10);
gfx::Point point_in_b_frame =
rwhv_b->TransformPointToRootCoordSpace(gfx::Point(25, 25));
// Create listeners for mouse events. These are used to verify that the
// RenderWidgetHostInputEventRouter is generating MouseLeave, etc for
// the right renderers.
RenderWidgetHostMouseEventMonitor a_frame_monitor(
root->current_frame_host()->GetRenderWidgetHost());
RenderWidgetHostMouseEventMonitor b_frame_monitor(
b_node->current_frame_host()->GetRenderWidgetHost());
// Add tooltip text to both the body and the iframe in A.
std::string script_a =
"body = document.body.setAttribute('title', 'body_a_tooltip');\n"
"iframe = document.getElementsByTagName('iframe')[0];\n"
"iframe.setAttribute('title','iframe_for_b');";
EXPECT_TRUE(ExecuteScript(root->current_frame_host(), script_a));
std::string script_b =
"body = document.body.setAttribute('title', 'body_b_tooltip');";
EXPECT_TRUE(ExecuteScript(b_node->current_frame_host(), script_b));
// Send mouse events to both A and B.
blink::WebMouseEvent mouse_event(
blink::WebInputEvent::kMouseMove, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
auto* router = web_contents()->GetInputEventRouter();
// Alternate mouse moves between main frame and the cross-process iframe to
// test that the tool tip in the iframe can override the one set by the main
// frame renderer, even on a second entry into the iframe.
gfx::Point current_point;
for (int iteration = 0; iteration < 2; ++iteration) {
// The following is a bit of a hack to prevent hitting the same
// position/node check in ChromeClient::SetToolTip().
current_point = point_in_a_frame;
current_point.Offset(iteration, iteration);
SetWebEventPositions(&mouse_event, current_point, rwhv_a);
RouteMouseEventAndWaitUntilDispatch(router, rwhv_a, rwhv_a, &mouse_event);
EXPECT_TRUE(a_frame_monitor.EventWasReceived());
a_frame_monitor.ResetEventReceived();
// B will receive a mouseLeave on all but the first iteration.
EXPECT_EQ(iteration != 0, b_frame_monitor.EventWasReceived());
b_frame_monitor.ResetEventReceived();
// Next send a MouseMove to B frame, and A should receive a MouseMove event.
current_point = point_in_b_frame;
current_point.Offset(iteration, iteration);
SetWebEventPositions(&mouse_event, current_point, rwhv_a);
RouteMouseEventAndWaitUntilDispatch(router, rwhv_a, rwhv_b, &mouse_event);
EXPECT_TRUE(a_frame_monitor.EventWasReceived());
EXPECT_EQ(a_frame_monitor.event().GetType(),
blink::WebInputEvent::kMouseMove);
a_frame_monitor.ResetEventReceived();
EXPECT_TRUE(b_frame_monitor.EventWasReceived());
b_frame_monitor.ResetEventReceived();
}
// This is an (arbitrary) delay to allow the test to crash if it's going to.
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), TestTimeouts::action_max_timeout());
run_loop.Run();
}
#endif // defined(OS_ANDROID)
// This test verifies that MouseEnter and MouseLeave events fire correctly
// when the mouse cursor moves between processes.
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
CrossProcessMouseEnterAndLeaveTest) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b,c(d))"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
RenderFrameSubmissionObserver render_frame_submission_observer(
shell()->web_contents());
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
EXPECT_EQ(
" Site A ------------ proxies for B C D\n"
" |--Site B ------- proxies for A C D\n"
" +--Site C ------- proxies for A B D\n"
" +--Site D -- proxies for A B C\n"
"Where A = http://a.com/\n"
" B = http://b.com/\n"
" C = http://c.com/\n"
" D = http://d.com/",
DepictFrameTree(root));
FrameTreeNode* b_node = root->child_at(0);
FrameTreeNode* c_node = root->child_at(1);
FrameTreeNode* d_node = c_node->child_at(0);
RenderWidgetHostViewBase* rwhv_a = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_b = static_cast<RenderWidgetHostViewBase*>(
b_node->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_d = static_cast<RenderWidgetHostViewBase*>(
d_node->current_frame_host()->GetRenderWidgetHost()->GetView());
// Verifying surfaces are ready in B and D are sufficient, since other
// surfaces contain at least one of them.
WaitForHitTestDataOrChildSurfaceReady(b_node->current_frame_host());
WaitForHitTestDataOrChildSurfaceReady(d_node->current_frame_host());
// Create listeners for mouse events. These are used to verify that the
// RenderWidgetHostInputEventRouter is generating MouseLeave, etc for
// the right renderers.
RenderWidgetHostMouseEventMonitor root_frame_monitor(
root->current_frame_host()->GetRenderWidgetHost());
RenderWidgetHostMouseEventMonitor a_frame_monitor(
root->current_frame_host()->GetRenderWidgetHost());
RenderWidgetHostMouseEventMonitor b_frame_monitor(
b_node->current_frame_host()->GetRenderWidgetHost());
RenderWidgetHostMouseEventMonitor c_frame_monitor(
c_node->current_frame_host()->GetRenderWidgetHost());
RenderWidgetHostMouseEventMonitor d_frame_monitor(
d_node->current_frame_host()->GetRenderWidgetHost());
float scale_factor =
render_frame_submission_observer.LastRenderFrameMetadata()
.page_scale_factor;
// Get the view bounds of the child iframe, which should account for the
// relative offset of its direct parent within the root frame, for use in
// targeting the input event.
gfx::Rect a_bounds = rwhv_a->GetViewBounds();
gfx::Rect b_bounds = rwhv_b->GetViewBounds();
gfx::Rect d_bounds = rwhv_d->GetViewBounds();
gfx::Point point_in_a_frame(2, 2);
gfx::Point point_in_b_frame(
gfx::ToCeiledInt((b_bounds.x() - a_bounds.x() + 25) * scale_factor),
gfx::ToCeiledInt((b_bounds.y() - a_bounds.y() + 25) * scale_factor));
gfx::Point point_in_d_frame(
gfx::ToCeiledInt((d_bounds.x() - a_bounds.x() + 25) * scale_factor),
gfx::ToCeiledInt((d_bounds.y() - a_bounds.y() + 25) * scale_factor));
blink::WebMouseEvent mouse_event(
blink::WebInputEvent::kMouseMove, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
SetWebEventPositions(&mouse_event, point_in_a_frame, rwhv_a);
auto* router = web_contents()->GetInputEventRouter();
// Send an initial MouseMove to the root view, which shouldn't affect the
// other renderers.
RouteMouseEventAndWaitUntilDispatch(router, rwhv_a, rwhv_a, &mouse_event);
EXPECT_TRUE(a_frame_monitor.EventWasReceived());
a_frame_monitor.ResetEventReceived();
EXPECT_FALSE(b_frame_monitor.EventWasReceived());
EXPECT_FALSE(c_frame_monitor.EventWasReceived());
EXPECT_FALSE(d_frame_monitor.EventWasReceived());
// Next send a MouseMove to B frame, which shouldn't affect C or D but
// A should receive a MouseMove event.
SetWebEventPositions(&mouse_event, point_in_b_frame, rwhv_a);
RouteMouseEventAndWaitUntilDispatch(router, rwhv_a, rwhv_b, &mouse_event);
EXPECT_TRUE(a_frame_monitor.EventWasReceived());
EXPECT_EQ(a_frame_monitor.event().GetType(),
blink::WebInputEvent::kMouseMove);
a_frame_monitor.ResetEventReceived();
EXPECT_TRUE(b_frame_monitor.EventWasReceived());
b_frame_monitor.ResetEventReceived();
EXPECT_FALSE(c_frame_monitor.EventWasReceived());
EXPECT_FALSE(d_frame_monitor.EventWasReceived());
// Next send a MouseMove to D frame, which should have side effects in every
// other RenderWidgetHostView.
SetWebEventPositions(&mouse_event, point_in_d_frame, rwhv_a);
RouteMouseEventAndWaitUntilDispatch(router, rwhv_a, rwhv_d, &mouse_event);
EXPECT_TRUE(a_frame_monitor.EventWasReceived());
EXPECT_EQ(a_frame_monitor.event().GetType(),
blink::WebInputEvent::kMouseMove);
EXPECT_TRUE(b_frame_monitor.EventWasReceived());
EXPECT_EQ(b_frame_monitor.event().GetType(),
blink::WebInputEvent::kMouseLeave);
EXPECT_TRUE(c_frame_monitor.EventWasReceived());
EXPECT_EQ(c_frame_monitor.event().GetType(),
blink::WebInputEvent::kMouseMove);
EXPECT_TRUE(d_frame_monitor.EventWasReceived());
}
// Verify that mouse capture works on a RenderWidgetHostView level.
// This test checks that a MouseDown triggers mouse capture when it hits
// a scrollbar thumb or a subframe, and does not trigger mouse
// capture if it hits an element in the main frame.
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
CrossProcessMouseCapture) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_large_scrollable_frame.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
RenderFrameSubmissionObserver render_frame_submission_observer(
shell()->web_contents());
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
ASSERT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://127.0.0.1/\n"
" B = http://baz.com/",
DepictFrameTree(root));
// Create listeners for mouse events.
RenderWidgetHostMouseEventMonitor main_frame_monitor(
root->current_frame_host()->GetRenderWidgetHost());
RenderWidgetHostMouseEventMonitor child_frame_monitor(
child_node->current_frame_host()->GetRenderWidgetHost());
RenderWidgetHostInputEventRouter* router =
web_contents()->GetInputEventRouter();
RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
float scale_factor =
render_frame_submission_observer.LastRenderFrameMetadata()
.page_scale_factor;
// Get the view bounds of the child iframe, which should account for the
// relative offset of its direct parent within the root frame, for use in
// targeting the input event.
gfx::Rect bounds = rwhv_child->GetViewBounds();
int child_frame_target_x = gfx::ToCeiledInt(
(bounds.x() - root_view->GetViewBounds().x() + 5) * scale_factor);
int child_frame_target_y = gfx::ToCeiledInt(
(bounds.y() - root_view->GetViewBounds().y() + 5) * scale_factor);
scoped_refptr<SetMouseCaptureInterceptor> child_interceptor =
new SetMouseCaptureInterceptor(static_cast<RenderWidgetHostImpl*>(
child_node->current_frame_host()->GetRenderWidgetHost()));
// Target MouseDown to child frame.
blink::WebMouseEvent mouse_event(
blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
mouse_event.button = blink::WebPointerProperties::Button::kLeft;
SetWebEventPositions(&mouse_event,
gfx::Point(child_frame_target_x, child_frame_target_y),
root_view);
mouse_event.click_count = 1;
main_frame_monitor.ResetEventReceived();
child_frame_monitor.ResetEventReceived();
RouteMouseEventAndWaitUntilDispatch(router, root_view, rwhv_child,
&mouse_event);
EXPECT_FALSE(main_frame_monitor.EventWasReceived());
EXPECT_TRUE(child_frame_monitor.EventWasReceived());
// Wait for the mouse capture message.
child_interceptor->Wait();
EXPECT_TRUE(child_interceptor->Capturing());
// Yield the thread, in order to let the capture message be processed by its
// actual handler.
base::RunLoop().RunUntilIdle();
// Target MouseMove at main frame. The child frame is now capturing input,
// so it should receive the event instead.
mouse_event.SetType(blink::WebInputEvent::kMouseMove);
mouse_event.SetModifiers(blink::WebInputEvent::kLeftButtonDown);
SetWebEventPositions(&mouse_event, gfx::Point(1, 1), root_view);
RouteMouseEventAndWaitUntilDispatch(router, root_view, rwhv_child,
&mouse_event);
// Dispatch twice because the router generates an extra MouseLeave for the
// main frame.
main_frame_monitor.ResetEventReceived();
child_frame_monitor.ResetEventReceived();
RouteMouseEventAndWaitUntilDispatch(router, root_view, rwhv_child,
&mouse_event);
EXPECT_FALSE(main_frame_monitor.EventWasReceived());
EXPECT_TRUE(child_frame_monitor.EventWasReceived());
// MouseUp releases capture.
mouse_event.SetType(blink::WebInputEvent::kMouseUp);
mouse_event.SetModifiers(blink::WebInputEvent::kNoModifiers);
SetWebEventPositions(&mouse_event, gfx::Point(1, 1), root_view);
RouteMouseEventAndWaitUntilDispatch(router, root_view, rwhv_child,
&mouse_event);
child_interceptor->Wait();
EXPECT_FALSE(child_interceptor->Capturing());
// Targeting a MouseDown to the main frame should not initiate capture.
mouse_event.SetType(blink::WebInputEvent::kMouseDown);
mouse_event.SetModifiers(blink::WebInputEvent::kLeftButtonDown);
mouse_event.button = blink::WebPointerProperties::Button::kLeft;
SetWebEventPositions(&mouse_event, gfx::Point(1, 1), root_view);
mouse_event.click_count = 1;
main_frame_monitor.ResetEventReceived();
child_frame_monitor.ResetEventReceived();
RouteMouseEventAndWaitUntilDispatch(router, root_view, root_view,
&mouse_event);
EXPECT_TRUE(main_frame_monitor.EventWasReceived());
EXPECT_FALSE(child_frame_monitor.EventWasReceived());
// Target MouseMove at child frame. Without capture, this should be
// dispatched to the child frame.
mouse_event.SetType(blink::WebInputEvent::kMouseMove);
SetWebEventPositions(&mouse_event,
gfx::Point(child_frame_target_x, child_frame_target_y),
root_view);
RouteMouseEventAndWaitUntilDispatch(router, root_view, rwhv_child,
&mouse_event);
main_frame_monitor.ResetEventReceived();
child_frame_monitor.ResetEventReceived();
// Again, twice because of the transition MouseMove sent to the main
// frame.
RouteMouseEventAndWaitUntilDispatch(router, root_view, rwhv_child,
&mouse_event);
EXPECT_FALSE(main_frame_monitor.EventWasReceived());
EXPECT_TRUE(child_frame_monitor.EventWasReceived());
EXPECT_FALSE(child_interceptor->Capturing());
// No release capture events since the capture statu doesn't change.
mouse_event.SetType(blink::WebInputEvent::kMouseUp);
mouse_event.SetModifiers(blink::WebInputEvent::kNoModifiers);
SetWebEventPositions(&mouse_event,
gfx::Point(child_frame_target_x, child_frame_target_y),
root_view);
RouteMouseEventAndWaitUntilDispatch(router, root_view, rwhv_child,
&mouse_event);
EXPECT_FALSE(child_interceptor->Capturing());
base::RunLoop().RunUntilIdle();
// Targeting a scrollbar with a click doesn't work on Mac or Android.
#if !defined(OS_MACOSX) && !defined(OS_ANDROID)
scoped_refptr<SetMouseCaptureInterceptor> root_interceptor =
new SetMouseCaptureInterceptor(static_cast<RenderWidgetHostImpl*>(
root->current_frame_host()->GetRenderWidgetHost()));
// Now send a MouseDown to target the thumb part of the scroll bar, which
// should initiate mouse capture for the main frame.
mouse_event.SetType(blink::WebInputEvent::kMouseDown);
mouse_event.SetModifiers(blink::WebInputEvent::kLeftButtonDown);
SetWebEventPositions(&mouse_event, gfx::Point(100, 25), root_view);
RouteMouseEventAndWaitUntilDispatch(router, root_view, root_view,
&mouse_event);
EXPECT_TRUE(main_frame_monitor.EventWasReceived());
// Wait for the mouse capture message.
root_interceptor->Wait();
EXPECT_TRUE(root_interceptor->Capturing());
base::RunLoop().RunUntilIdle();
main_frame_monitor.ResetEventReceived();
child_frame_monitor.ResetEventReceived();
// Now that the main frame is capturing, a MouseMove targeted to the child
// frame should be received by the main frame.
mouse_event.SetType(blink::WebInputEvent::kMouseMove);
SetWebEventPositions(&mouse_event,
gfx::Point(child_frame_target_x, child_frame_target_y),
root_view);
RouteMouseEventAndWaitUntilDispatch(router, root_view, root_view,
&mouse_event);
main_frame_monitor.ResetEventReceived();
child_frame_monitor.ResetEventReceived();
RouteMouseEventAndWaitUntilDispatch(router, root_view, root_view,
&mouse_event);
EXPECT_TRUE(main_frame_monitor.EventWasReceived());
EXPECT_FALSE(child_frame_monitor.EventWasReceived());
// A MouseUp sent anywhere should cancel the mouse capture.
mouse_event.SetType(blink::WebInputEvent::kMouseUp);
mouse_event.SetModifiers(blink::WebInputEvent::kNoModifiers);
SetWebEventPositions(&mouse_event,
gfx::Point(child_frame_target_x, child_frame_target_y),
root_view);
RouteMouseEventAndWaitUntilDispatch(router, root_view, root_view,
&mouse_event);
root_interceptor->Wait();
EXPECT_FALSE(root_interceptor->Capturing());
#endif // !defined(OS_MACOSX) && !defined(OS_ANDROID)
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
MouseCaptureOnDragSelection) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
RenderFrameSubmissionObserver render_frame_submission_observer(
shell()->web_contents());
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
ASSERT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://127.0.0.1/\n"
" B = http://baz.com/",
DepictFrameTree(root));
// Create listeners for mouse events.
RenderWidgetHostMouseEventMonitor main_frame_monitor(
root->current_frame_host()->GetRenderWidgetHost());
RenderWidgetHostMouseEventMonitor child_frame_monitor(
child_node->current_frame_host()->GetRenderWidgetHost());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
scoped_refptr<SetMouseCaptureInterceptor> interceptor =
new SetMouseCaptureInterceptor(static_cast<RenderWidgetHostImpl*>(
child_node->current_frame_host()->GetRenderWidgetHost()));
// Target MouseDown to child frame.
blink::WebMouseEvent mouse_event(
blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
mouse_event.button = blink::WebPointerProperties::Button::kLeft;
mouse_event.click_count = 1;
main_frame_monitor.ResetEventReceived();
child_frame_monitor.ResetEventReceived();
DispatchMouseEventAndWaitUntilDispatch(web_contents(), mouse_event,
rwhv_child, gfx::PointF(15.0, 5.0),
rwhv_child, gfx::PointF(15.0, 5.0));
EXPECT_FALSE(main_frame_monitor.EventWasReceived());
EXPECT_TRUE(child_frame_monitor.EventWasReceived());
// Wait for the mouse capture message.
interceptor->Wait();
EXPECT_TRUE(interceptor->Capturing());
main_frame_monitor.ResetEventReceived();
child_frame_monitor.ResetEventReceived();
// Target MouseMove to child frame to start drag. This should cause the
// child to start capturing mouse input.
mouse_event.SetType(blink::WebInputEvent::kMouseMove);
mouse_event.SetModifiers(blink::WebInputEvent::kLeftButtonDown);
DispatchMouseEventAndWaitUntilDispatch(web_contents(), mouse_event,
rwhv_child, gfx::PointF(5.0, 5.0),
rwhv_child, gfx::PointF(5.0, 5.0));
// Dispatch twice because the router generates an extra MouseLeave for the
// main frame.
main_frame_monitor.ResetEventReceived();
child_frame_monitor.ResetEventReceived();
DispatchMouseEventAndWaitUntilDispatch(web_contents(), mouse_event,
rwhv_child, gfx::PointF(5.0, 5.0),
rwhv_child, gfx::PointF(5.0, 5.0));
EXPECT_FALSE(main_frame_monitor.EventWasReceived());
EXPECT_TRUE(child_frame_monitor.EventWasReceived());
main_frame_monitor.ResetEventReceived();
child_frame_monitor.ResetEventReceived();
EXPECT_TRUE(interceptor->Capturing());
// Yield the thread, in order to let the capture message be processed by its
// actual handler.
{
base::RunLoop loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
loop.QuitClosure());
loop.Run();
}
// Now that the child frame is capturing, a MouseMove targeted to the main
// frame should be received by the child frame.
DispatchMouseEventAndWaitUntilDispatch(web_contents(), mouse_event,
rwhv_child, gfx::PointF(-25.0, -25.0),
rwhv_child, gfx::PointF(-25.0, -25.0));
EXPECT_FALSE(main_frame_monitor.EventWasReceived());
EXPECT_TRUE(child_frame_monitor.EventWasReceived());
main_frame_monitor.ResetEventReceived();
child_frame_monitor.ResetEventReceived();
// A MouseUp sent anywhere should cancel the mouse capture.
mouse_event.SetType(blink::WebInputEvent::kMouseUp);
mouse_event.SetModifiers(0);
DispatchMouseEventAndWaitUntilDispatch(web_contents(), mouse_event,
rwhv_child, gfx::PointF(-25.0, -25.0),
rwhv_child, gfx::PointF(-25.0, -25.0));
interceptor->Wait();
EXPECT_FALSE(interceptor->Capturing());
}
// Verify that when a divider within a frameset is clicked, mouse capture is
// initiated.
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
MouseCaptureOnFramesetResize) {
GURL main_url(embedded_test_server()->GetURL("/page_with_frameset.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
RenderWidgetHost* widget_host =
root->current_frame_host()->GetRenderWidgetHost();
RenderWidgetHostViewBase* rwhv_root =
static_cast<RenderWidgetHostViewBase*>(widget_host->GetView());
scoped_refptr<SetMouseCaptureInterceptor> interceptor =
new SetMouseCaptureInterceptor(
static_cast<RenderWidgetHostImpl*>(widget_host));
gfx::PointF click_point =
gfx::PointF(rwhv_root->GetViewBounds().width() / 2, 20);
// Click on the divider bar that initiates resize.
DispatchMouseEventAndWaitUntilDispatch(web_contents(), rwhv_root, click_point,
rwhv_root, click_point);
// Wait for the mouse capture message.
interceptor->Wait();
EXPECT_TRUE(interceptor->Capturing());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
CrossProcessMousePointerCapture) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_iframe_in_div.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
RenderFrameSubmissionObserver render_frame_submission_observer(
shell()->web_contents());
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
FrameTreeNode* child_node = root->child_at(0);
ASSERT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://127.0.0.1/\n"
" B = http://bar.com/",
DepictFrameTree(root));
ASSERT_TRUE(ExecuteScript(root,
" document.addEventListener('pointerdown', (e) => {"
" e.target.setPointerCapture(e.pointerId);"
"});"));
// Create listeners for mouse events.
RenderWidgetHostMouseEventMonitor main_frame_monitor(
root->current_frame_host()->GetRenderWidgetHost());
RenderWidgetHostMouseEventMonitor child_frame_monitor(
child_node->current_frame_host()->GetRenderWidgetHost());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
RenderWidgetHostInputEventRouter* router =
web_contents()->GetInputEventRouter();
RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* child_view = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
scoped_refptr<SetMouseCaptureInterceptor> root_interceptor =
new SetMouseCaptureInterceptor(static_cast<RenderWidgetHostImpl*>(
root->current_frame_host()->GetRenderWidgetHost()));
// Target MouseDown to main frame.
blink::WebMouseEvent mouse_event(
blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
mouse_event.button = blink::WebPointerProperties::Button::kLeft;
mouse_event.SetModifiers(blink::WebInputEvent::kLeftButtonDown);
mouse_event.pointer_type = blink::WebPointerProperties::PointerType::kMouse;
SetWebEventPositions(&mouse_event, gfx::Point(1, 1), root_view);
mouse_event.click_count = 1;
main_frame_monitor.ResetEventReceived();
child_frame_monitor.ResetEventReceived();
RouteMouseEventAndWaitUntilDispatch(router, root_view, root_view,
&mouse_event);
EXPECT_TRUE(main_frame_monitor.EventWasReceived());
EXPECT_FALSE(child_frame_monitor.EventWasReceived());
// Wait for the mouse capture message.
root_interceptor->Wait();
EXPECT_TRUE(root_interceptor->Capturing());
base::RunLoop().RunUntilIdle();
// Target MouseMove at child frame. The main frame is now capturing input,
// so it should receive the event instead.
float scale_factor =
render_frame_submission_observer.LastRenderFrameMetadata()
.page_scale_factor;
gfx::Rect bounds = child_view->GetViewBounds();
int child_frame_target_x = gfx::ToCeiledInt(
(bounds.x() - root_view->GetViewBounds().x() + 5) * scale_factor);
int child_frame_target_y = gfx::ToCeiledInt(
(bounds.y() - root_view->GetViewBounds().y() + 5) * scale_factor);
mouse_event.SetType(blink::WebInputEvent::kMouseMove);
mouse_event.SetModifiers(blink::WebInputEvent::kLeftButtonDown);
SetWebEventPositions(&mouse_event,
gfx::Point(child_frame_target_x, child_frame_target_y),
root_view);
main_frame_monitor.ResetEventReceived();
child_frame_monitor.ResetEventReceived();
RouteMouseEventAndWaitUntilDispatch(router, root_view, root_view,
&mouse_event);
EXPECT_TRUE(main_frame_monitor.EventWasReceived());
EXPECT_FALSE(child_frame_monitor.EventWasReceived());
// Add script to release capture and send a mouse move to triger it.
ASSERT_TRUE(ExecuteScript(root,
" document.addEventListener('pointermove', (e) => {"
" e.target.releasePointerCapture(e.pointerId);"
"});"));
main_frame_monitor.ResetEventReceived();
child_frame_monitor.ResetEventReceived();
RouteMouseEventAndWaitUntilDispatch(router, root_view, root_view,
&mouse_event);
EXPECT_TRUE(main_frame_monitor.EventWasReceived());
EXPECT_FALSE(child_frame_monitor.EventWasReceived());
// Mouse capture should be released now.
root_interceptor->Wait();
EXPECT_FALSE(root_interceptor->Capturing());
// Next move event should route to child frame.
RouteMouseEventAndWaitUntilDispatch(router, root_view, child_view,
&mouse_event);
// Dispatch twice because the router generates an extra MouseLeave for the
// main frame.
main_frame_monitor.ResetEventReceived();
child_frame_monitor.ResetEventReceived();
RouteMouseEventAndWaitUntilDispatch(router, root_view, child_view,
&mouse_event);
EXPECT_FALSE(main_frame_monitor.EventWasReceived());
EXPECT_TRUE(child_frame_monitor.EventWasReceived());
}
// There are no cursors on Android.
#if !defined(OS_ANDROID)
class CursorMessageFilter : public content::BrowserMessageFilter {
public:
CursorMessageFilter()
: content::BrowserMessageFilter(WidgetMsgStart),
message_loop_runner_(new content::MessageLoopRunner),
last_set_cursor_routing_id_(MSG_ROUTING_NONE) {}
bool OnMessageReceived(const IPC::Message& message) override {
if (message.type() == WidgetHostMsg_SetCursor::ID) {
base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::UI},
base::BindOnce(&CursorMessageFilter::OnSetCursor,
this, message.routing_id()));
}
return false;
}
void OnSetCursor(int routing_id) {
last_set_cursor_routing_id_ = routing_id;
message_loop_runner_->Quit();
}
int last_set_cursor_routing_id() const { return last_set_cursor_routing_id_; }
void Wait() {
// Do not reset the cursor, as the cursor may already have been set (and
// Quit() already called on |message_loop_runner_|).
message_loop_runner_->Run();
}
private:
~CursorMessageFilter() override {}
scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
int last_set_cursor_routing_id_;
DISALLOW_COPY_AND_ASSIGN(CursorMessageFilter);
};
namespace {
// Verify that we receive a mouse cursor update message when we mouse over
// a text field contained in an out-of-process iframe.
void CursorUpdateReceivedFromCrossSiteIframeHelper(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/page_with_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
auto* web_contents = static_cast<WebContentsImpl*>(shell->web_contents());
FrameTreeNode* root = web_contents->GetFrameTree()->root();
FrameTreeNode* child_node = root->child_at(0);
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
scoped_refptr<CursorMessageFilter> filter = new CursorMessageFilter();
child_node->current_frame_host()->GetProcess()->AddFilter(filter.get());
RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHost* rwh_child =
root->child_at(0)->current_frame_host()->GetRenderWidgetHost();
RenderWidgetHostViewBase* child_view =
static_cast<RenderWidgetHostViewBase*>(rwh_child->GetView());
// This should only return nullptr on Android.
EXPECT_TRUE(root_view->GetCursorManager());
WebCursor cursor;
EXPECT_FALSE(
root_view->GetCursorManager()->GetCursorForTesting(root_view, cursor));
EXPECT_FALSE(
root_view->GetCursorManager()->GetCursorForTesting(child_view, cursor));
// Send a MouseMove to the subframe. The frame contains text, and moving the
// mouse over it should cause the renderer to send a mouse cursor update.
blink::WebMouseEvent mouse_event(
blink::WebInputEvent::kMouseMove, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
SetWebEventPositions(&mouse_event, gfx::Point(60, 60), root_view);
auto* router = web_contents->GetInputEventRouter();
RenderWidgetHostMouseEventMonitor child_monitor(
child_view->GetRenderWidgetHost());
RenderWidgetHostMouseEventMonitor root_monitor(
root_view->GetRenderWidgetHost());
RouteMouseEventAndWaitUntilDispatch(router, root_view, child_view,
&mouse_event);
// The child_view should receive a mouse-move event.
EXPECT_TRUE(child_monitor.EventWasReceived());
EXPECT_EQ(blink::WebInputEvent::kMouseMove, child_monitor.event().GetType());
EXPECT_NEAR(8, child_monitor.event().PositionInWidget().x, kHitTestTolerance);
EXPECT_NEAR(8, child_monitor.event().PositionInWidget().y, kHitTestTolerance);
// The root_view should also receive a mouse-move event.
EXPECT_TRUE(root_monitor.EventWasReceived());
EXPECT_EQ(blink::WebInputEvent::kMouseMove, root_monitor.event().GetType());
EXPECT_EQ(60, root_monitor.event().PositionInWidget().x);
EXPECT_EQ(60, root_monitor.event().PositionInWidget().y);
// CursorMessageFilter::Wait() implicitly tests whether we receive a
// WidgetHostMsg_SetCursor message from the renderer process, because it does
// does not return otherwise.
filter->Wait();
EXPECT_EQ(filter->last_set_cursor_routing_id(), rwh_child->GetRoutingID());
// Yield to ensure that the SetCursor message is processed by its real
// handler.
{
base::RunLoop loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
loop.QuitClosure());
loop.Run();
}
// The root_view receives a mouse-move event on top of the iframe, which does
// not send a cursor update.
EXPECT_FALSE(
root_view->GetCursorManager()->GetCursorForTesting(root_view, cursor));
EXPECT_TRUE(
root_view->GetCursorManager()->GetCursorForTesting(child_view, cursor));
// Since this moused over a text box, this should not be the default cursor.
EXPECT_EQ(cursor.info().type, blink::WebCursorInfo::kTypeIBeam);
}
} // namespace
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
CursorUpdateReceivedFromCrossSiteIframe) {
CursorUpdateReceivedFromCrossSiteIframeHelper(shell(),
embedded_test_server());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest,
CursorUpdateReceivedFromCrossSiteIframe) {
CursorUpdateReceivedFromCrossSiteIframeHelper(shell(),
embedded_test_server());
}
#endif // !defined(OS_ANDROID)
#if defined(USE_AURA)
// Browser process hit testing is not implemented on Android, and these tests
// require Aura for RenderWidgetHostViewAura::OnTouchEvent().
// https://crbug.com/491334
// Ensure that scroll events can be cancelled with a wheel handler.
// https://crbug.com/698195
class SitePerProcessMouseWheelHitTestBrowserTest
: public SitePerProcessHitTestBrowserTest {
public:
SitePerProcessMouseWheelHitTestBrowserTest() : rwhv_root_(nullptr) {}
void SetupWheelAndScrollHandlers(content::RenderFrameHostImpl* rfh) {
// Set up event handlers. The wheel event handler calls prevent default on
// alternate events, so only every other wheel generates a scroll. The fact
// that any scroll events fire is dependent on the event going to the main
// thread, which requires the nonFastScrollableRegion be set correctly
// on the compositor.
std::string script =
"wheel_count = 0;"
"function wheel_handler(e) {"
" wheel_count++;"
" if (wheel_count % 2 == 0)"
" e.preventDefault();\n"
" domAutomationController.send('wheel: ' + wheel_count);"
"}"
"function scroll_handler(e) {"
" domAutomationController.send('scroll: ' + wheel_count);"
"}"
"scroll_div = document.getElementById('scrollable_div');"
"scroll_div.addEventListener('wheel', wheel_handler);"
"scroll_div.addEventListener('scroll', scroll_handler);"
"document.body.style.background = 'black';";
content::DOMMessageQueue msg_queue;
std::string reply;
EXPECT_TRUE(ExecuteScript(rfh, script));
// Wait until renderer's compositor thread is synced. Otherwise the event
// handler won't be installed when the event arrives.
{
MainThreadFrameObserver observer(rfh->GetRenderWidgetHost());
observer.Wait();
}
}
void SendMouseWheel(gfx::Point location) {
DCHECK(rwhv_root_);
ui::ScrollEvent scroll_event(ui::ET_SCROLL, location, ui::EventTimeForNow(),
0, 0, -ui::MouseWheelEvent::kWheelDelta, 0,
ui::MouseWheelEvent::kWheelDelta,
2); // This must be '2' or it gets silently
// dropped.
UpdateEventRootLocation(&scroll_event, rwhv_root_);
rwhv_root_->OnScrollEvent(&scroll_event);
}
void set_rwhv_root(RenderWidgetHostViewAura* rwhv_root) {
rwhv_root_ = rwhv_root;
}
void RunTest(gfx::Point pos, RenderWidgetHostViewBase* expected_target) {
content::DOMMessageQueue msg_queue;
std::string reply;
auto* rwhv_root = static_cast<RenderWidgetHostViewAura*>(
web_contents()->GetRenderWidgetHostView());
set_rwhv_root(rwhv_root);
// Set the wheel scroll latching timeout to a large value to make sure
// that the timer doesn't expire for the duration of the test.
rwhv_root->event_handler()->set_mouse_wheel_wheel_phase_handler_timeout(
TestTimeouts::action_max_timeout());
InputEventAckWaiter waiter(expected_target->GetRenderWidgetHost(),
blink::WebInputEvent::kMouseWheel);
SendMouseWheel(pos);
waiter.Wait();
// Expect both wheel and scroll handlers to fire.
EXPECT_TRUE(msg_queue.WaitForMessage(&reply));
EXPECT_EQ("\"wheel: 1\"", reply);
EXPECT_TRUE(msg_queue.WaitForMessage(&reply));
EXPECT_EQ("\"scroll: 1\"", reply);
SendMouseWheel(pos);
// Even though even number events are prevented by default since the first
// wheel event is not prevented by default, the rest of the wheel events
// will be handled nonblocking and the scroll will happen.
EXPECT_TRUE(msg_queue.WaitForMessage(&reply));
EXPECT_EQ("\"wheel: 2\"", reply);
EXPECT_TRUE(msg_queue.WaitForMessage(&reply));
EXPECT_EQ("\"scroll: 2\"", reply);
SendMouseWheel(pos);
// Odd number of wheels, expect both wheel and scroll handlers to fire
// again.
EXPECT_TRUE(msg_queue.WaitForMessage(&reply));
EXPECT_EQ("\"wheel: 3\"", reply);
EXPECT_TRUE(msg_queue.WaitForMessage(&reply));
EXPECT_EQ("\"scroll: 3\"", reply);
}
private:
RenderWidgetHostViewAura* rwhv_root_;
};
// Fails on Windows official build, see // https://crbug.com/800822
#if defined(OS_WIN)
#define MAYBE_MultipleSubframeWheelEventsOnMainThread \
DISABLED_MultipleSubframeWheelEventsOnMainThread
#else
#define MAYBE_MultipleSubframeWheelEventsOnMainThread \
MultipleSubframeWheelEventsOnMainThread
#endif
IN_PROC_BROWSER_TEST_P(SitePerProcessMouseWheelHitTestBrowserTest,
MAYBE_MultipleSubframeWheelEventsOnMainThread) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_two_positioned_frames.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(2U, root->child_count());
GURL frame_url(embedded_test_server()->GetURL(
"b.com", "/page_with_scrollable_div.html"));
// To test for https://bugs.chromium.org/p/chromium/issues/detail?id=820232
// it's important that both subframes are in the same renderer process, so
// we load the same URL in each case.
NavigateFrameToURL(root->child_at(0), frame_url);
NavigateFrameToURL(root->child_at(1), frame_url);
for (int frame_index = 0; frame_index < 2; frame_index++) {
// Synchronize with the child and parent renderers to guarantee that the
// surface information required for event hit testing is ready.
RenderWidgetHostViewBase* child_rwhv =
static_cast<RenderWidgetHostViewBase*>(
root->child_at(frame_index)->current_frame_host()->GetView());
WaitForHitTestDataOrChildSurfaceReady(
root->child_at(frame_index)->current_frame_host());
content::RenderFrameHostImpl* child =
root->child_at(frame_index)->current_frame_host();
SetupWheelAndScrollHandlers(child);
gfx::Rect bounds = child_rwhv->GetViewBounds();
gfx::Point pos(bounds.x() + 10, bounds.y() + 10);
RunTest(pos, child_rwhv);
}
}
// Verifies that test in SubframeWheelEventsOnMainThread also makes sense for
// the same page loaded in the mainframe.
// Fails on Windows official build, see // https://crbug.com/800822
#if defined(OS_WIN)
#define MAYBE_MainframeWheelEventsOnMainThread \
DISABLED_MainframeWheelEventsOnMainThread
#else
#define MAYBE_MainframeWheelEventsOnMainThread MainframeWheelEventsOnMainThread
#endif
IN_PROC_BROWSER_TEST_P(SitePerProcessMouseWheelHitTestBrowserTest,
MAYBE_MainframeWheelEventsOnMainThread) {
GURL main_url(
embedded_test_server()->GetURL("/page_with_scrollable_div.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
content::RenderFrameHostImpl* rfhi = root->current_frame_host();
SetupWheelAndScrollHandlers(rfhi);
gfx::Point pos(10, 10);
RunTest(pos, rfhi->GetRenderWidgetHost()->GetView());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessMouseWheelHitTestBrowserTest,
InputEventRouterWheelTargetTest) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_nested_frames.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
auto* rwhv_root = static_cast<RenderWidgetHostViewAura*>(
web_contents()->GetRenderWidgetHostView());
set_rwhv_root(rwhv_root);
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
GURL frame_url(embedded_test_server()->GetURL(
"b.com", "/page_with_scrollable_div.html"));
NavigateFrameToURL(root->child_at(0), frame_url);
// Synchronize with the child and parent renderers to guarantee that the
// surface information required for event hit testing is ready.
RenderWidgetHostViewBase* child_rwhv = static_cast<RenderWidgetHostViewBase*>(
root->child_at(0)->current_frame_host()->GetView());
WaitForHitTestDataOrChildSurfaceReady(
root->child_at(0)->current_frame_host());
RenderWidgetHostInputEventRouter* router =
web_contents()->GetInputEventRouter();
// Send a mouse wheel event to child.
gfx::Rect bounds = child_rwhv->GetViewBounds();
gfx::Point pos(bounds.x() + 10, bounds.y() + 10);
InputEventAckWaiter waiter(child_rwhv->GetRenderWidgetHost(),
blink::WebInputEvent::kMouseWheel);
SendMouseWheel(pos);
waiter.Wait();
EXPECT_EQ(child_rwhv, router->wheel_target_);
// Send a mouse wheel event to the main frame. It will be still routed to
// child till the end of current scrolling sequence. Since wheel scroll
// latching is enabled by default, we always do sync targeting so
// InputEventAckWaiter is not needed here.
TestInputEventObserver child_frame_monitor(child_rwhv->GetRenderWidgetHost());
SendMouseWheel(pos);
EXPECT_EQ(child_rwhv, router->wheel_target_);
// Verify that this a mouse wheel event was sent to the child frame renderer.
EXPECT_TRUE(child_frame_monitor.EventWasReceived());
EXPECT_TRUE(base::ContainsValue(child_frame_monitor.events_received(),
blink::WebInputEvent::kMouseWheel));
// Kill the wheel target view process. This must reset the wheel_target_.
RenderProcessHost* child_process =
root->child_at(0)->current_frame_host()->GetProcess();
RenderProcessHostWatcher crash_observer(
child_process, RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
child_process->Shutdown(0);
crash_observer.Wait();
EXPECT_EQ(nullptr, router->wheel_target_);
}
// Ensure that the positions of mouse wheel events sent to cross-process
// subframes account for any change in the position of the subframe during the
// scroll sequence.
IN_PROC_BROWSER_TEST_P(SitePerProcessMouseWheelHitTestBrowserTest,
MouseWheelEventPositionChange) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_tall_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
auto* rwhv_root = static_cast<RenderWidgetHostViewAura*>(
web_contents()->GetRenderWidgetHostView());
set_rwhv_root(rwhv_root);
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
// Synchronize with the child and parent renderers to guarantee that the
// surface information required for event hit testing is ready.
RenderWidgetHostViewChildFrame* child_rwhv =
static_cast<RenderWidgetHostViewChildFrame*>(
root->child_at(0)->current_frame_host()->GetView());
WaitForHitTestDataOrChildSurfaceReady(
root->child_at(0)->current_frame_host());
RenderWidgetHostInputEventRouter* router =
web_contents()->GetInputEventRouter();
auto await_gesture_event_with_position = base::BindRepeating(
[](blink::WebInputEvent::Type expected_type,
RenderWidgetHostViewBase* rwhv, gfx::PointF expected_position,
gfx::PointF expected_position_in_root, InputEventAckSource,
InputEventAckState, const blink::WebInputEvent& event) {
if (event.GetType() != expected_type)
return false;
const auto& gesture_event =
static_cast<const blink::WebGestureEvent&>(event);
const gfx::PointF root_point = rwhv->TransformPointToRootCoordSpaceF(
gesture_event.PositionInWidget());
EXPECT_FLOAT_EQ(gesture_event.PositionInWidget().x,
expected_position.x());
EXPECT_FLOAT_EQ(gesture_event.PositionInWidget().y,
expected_position.y());
EXPECT_FLOAT_EQ(root_point.x(), expected_position_in_root.x());
EXPECT_FLOAT_EQ(root_point.y(), expected_position_in_root.y());
return true;
});
MainThreadFrameObserver thread_observer(rwhv_root->GetRenderWidgetHost());
// Send a mouse wheel begin event to child.
blink::WebMouseWheelEvent scroll_event(
blink::WebInputEvent::kMouseWheel, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
gfx::Point child_point_in_root(90, 90);
SetWebEventPositions(&scroll_event, child_point_in_root, rwhv_root);
scroll_event.delta_x = 0.0f;
scroll_event.delta_y = -20.0f;
scroll_event.phase = blink::WebMouseWheelEvent::kPhaseBegan;
scroll_event.has_precise_scrolling_deltas = true;
{
InputEventAckWaiter await_begin_in_child(
child_rwhv->GetRenderWidgetHost(),
base::BindRepeating(await_gesture_event_with_position,
blink::WebInputEvent::kGestureScrollBegin,
child_rwhv, gfx::PointF(38, 38),
gfx::PointF(child_point_in_root)));
InputEventAckWaiter await_update_in_child(
child_rwhv->GetRenderWidgetHost(),
base::BindRepeating(await_gesture_event_with_position,
blink::WebInputEvent::kGestureScrollUpdate,
child_rwhv, gfx::PointF(38, 38),
gfx::PointF(child_point_in_root)));
InputEventAckWaiter await_update_in_root(
rwhv_root->GetRenderWidgetHost(),
base::BindRepeating(await_gesture_event_with_position,
blink::WebInputEvent::kGestureScrollUpdate,
rwhv_root, gfx::PointF(child_point_in_root),
gfx::PointF(child_point_in_root)));
router->RouteMouseWheelEvent(rwhv_root, &scroll_event, ui::LatencyInfo());
await_begin_in_child.Wait();
await_update_in_child.Wait();
await_update_in_root.Wait();
thread_observer.Wait();
}
// Send mouse wheel update event to child.
{
scroll_event.phase = blink::WebMouseWheelEvent::kPhaseChanged;
InputEventAckWaiter await_update_in_child(
child_rwhv->GetRenderWidgetHost(),
base::BindRepeating(await_gesture_event_with_position,
blink::WebInputEvent::kGestureScrollUpdate,
child_rwhv, gfx::PointF(38, 58),
gfx::PointF(child_point_in_root)));
InputEventAckWaiter await_update_in_root(
rwhv_root->GetRenderWidgetHost(),
base::BindRepeating(await_gesture_event_with_position,
blink::WebInputEvent::kGestureScrollUpdate,
rwhv_root, gfx::PointF(child_point_in_root),
gfx::PointF(child_point_in_root)));
router->RouteMouseWheelEvent(rwhv_root, &scroll_event, ui::LatencyInfo());
await_update_in_child.Wait();
await_update_in_root.Wait();
thread_observer.Wait();
}
#if !defined(OS_WIN)
{
ui::ScrollEvent fling_start(ui::ET_SCROLL_FLING_START, child_point_in_root,
ui::EventTimeForNow(), 0, 10, 0, 10, 0, 1);
UpdateEventRootLocation(&fling_start, rwhv_root);
InputEventAckWaiter await_fling_start_in_child(
child_rwhv->GetRenderWidgetHost(),
base::BindRepeating(await_gesture_event_with_position,
blink::WebInputEvent::kGestureFlingStart,
child_rwhv, gfx::PointF(38, 78),
gfx::PointF(child_point_in_root)));
rwhv_root->OnScrollEvent(&fling_start);
await_fling_start_in_child.Wait();
thread_observer.Wait();
}
#endif
}
// Ensure that a cross-process subframe with a touch-handler can receive touch
// events.
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
SubframeTouchEventRouting) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_nested_frames.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
WebContentsImpl* contents = web_contents();
FrameTreeNode* root = contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
GURL frame_url(
embedded_test_server()->GetURL("b.com", "/page_with_touch_handler.html"));
NavigateFrameToURL(root->child_at(0), frame_url);
// Synchronize with the child and parent renderers to guarantee that the
// surface information required for event hit testing is ready.
WaitForHitTestDataOrChildSurfaceReady(
root->child_at(0)->current_frame_host());
// There's no intrinsic reason the following values can't be equal, but they
// aren't at present, and if they become the same this test will need to be
// updated to accommodate.
EXPECT_NE(cc::kTouchActionAuto, cc::kTouchActionNone);
// Verify the child's input router is initially not set. The TouchStart event
// will trigger kTouchActionNone being sent back to the browser.
RenderWidgetHostImpl* child_render_widget_host =
root->child_at(0)->current_frame_host()->GetRenderWidgetHost();
EXPECT_FALSE(child_render_widget_host->input_router()
->AllowedTouchAction()
.has_value());
InputEventAckWaiter waiter(child_render_widget_host,
blink::WebInputEvent::kTouchStart);
// Simulate touch event to sub-frame.
gfx::Point child_center(150, 150);
auto* rwhv = static_cast<RenderWidgetHostViewAura*>(
contents->GetRenderWidgetHostView());
// Wait until renderer's compositor thread is synced.
{
MainThreadFrameObserver observer(child_render_widget_host);
observer.Wait();
}
ui::TouchEvent touch_event(
ui::ET_TOUCH_PRESSED, child_center, ui::EventTimeForNow(),
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH,
/* pointer_id*/ 0,
/* radius_x */ 30.0f,
/* radius_y */ 30.0f,
/* force */ 0.0f));
UpdateEventRootLocation(&touch_event, rwhv);
rwhv->OnTouchEvent(&touch_event);
waiter.Wait();
{
MainThreadFrameObserver observer(child_render_widget_host);
observer.Wait();
}
// Verify touch handler in subframe was invoked.
std::string result;
EXPECT_TRUE(ExecuteScriptAndExtractString(
root->child_at(0),
"window.domAutomationController.send(getLastTouchEvent());", &result));
EXPECT_EQ("touchstart", result);
// Verify the presence of the touch handler in the child frame correctly
// propagates touch-action:none information back to the child's input router.
EXPECT_EQ(cc::kTouchActionNone,
child_render_widget_host->input_router()->AllowedTouchAction());
}
// This test verifies that the test in
// SitePerProcessHitTestBrowserTest.SubframeTouchEventRouting also works
// properly for the main frame. Prior to the CL in which this test is
// introduced, use of MainThreadFrameObserver in SubframeTouchEventRouting was
// not necessary since the touch events were handled on the main thread. Now
// they are handled on the compositor thread, hence the need to synchronize.
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
MainframeTouchEventRouting) {
GURL main_url(
embedded_test_server()->GetURL("/page_with_touch_handler.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
WebContentsImpl* contents = web_contents();
FrameTreeNode* root = contents->GetFrameTree()->root();
// Synchronize with the renderers to guarantee that the
// surface information required for event hit testing is ready.
auto* rwhv = static_cast<RenderWidgetHostViewAura*>(
contents->GetRenderWidgetHostView());
// There's no intrinsic reason the following values can't be equal, but they
// aren't at present, and if they become the same this test will need to be
// updated to accommodate.
EXPECT_NE(cc::kTouchActionAuto, cc::kTouchActionNone);
// Verify the main frame's input router is initially not set. The
// TouchStart event will trigger kTouchActionNone being sent back to the
// browser.
RenderWidgetHostImpl* render_widget_host =
root->current_frame_host()->GetRenderWidgetHost();
EXPECT_FALSE(
render_widget_host->input_router()->AllowedTouchAction().has_value());
// Simulate touch event to sub-frame.
gfx::Point frame_center(150, 150);
// Wait until renderer's compositor thread is synced.
{
auto observer =
std::make_unique<MainThreadFrameObserver>(render_widget_host);
observer->Wait();
}
ui::TouchEvent touch_event(
ui::ET_TOUCH_PRESSED, frame_center, ui::EventTimeForNow(),
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH,
/* pointer_id*/ 0,
/* radius_x */ 30.0f,
/* radius_y */ 30.0f,
/* force */ 0.0f));
UpdateEventRootLocation(&touch_event, rwhv);
rwhv->OnTouchEvent(&touch_event);
{
auto observer =
std::make_unique<MainThreadFrameObserver>(render_widget_host);
observer->Wait();
}
// Verify touch handler in subframe was invoked.
std::string result;
EXPECT_TRUE(ExecuteScriptAndExtractString(
root, "window.domAutomationController.send(getLastTouchEvent());",
&result));
EXPECT_EQ("touchstart", result);
// Verify the presence of the touch handler in the child frame correctly
// propagates touch-action:none information back to the child's input router.
EXPECT_EQ(cc::kTouchActionNone,
render_widget_host->input_router()->AllowedTouchAction());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
SubframeGestureEventRouting) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_nested_frames.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
GURL frame_url(
embedded_test_server()->GetURL("b.com", "/page_with_click_handler.html"));
NavigateFrameToURL(root->child_at(0), frame_url);
auto* child_frame_host = root->child_at(0)->current_frame_host();
// Synchronize with the child and parent renderers to guarantee that the
// surface information required for event hit testing is ready.
WaitForHitTestDataOrChildSurfaceReady(child_frame_host);
// There have been no GestureTaps sent yet.
{
std::string result;
EXPECT_TRUE(ExecuteScriptAndExtractString(
child_frame_host,
"window.domAutomationController.send(getClickStatus());", &result));
EXPECT_EQ("0 clicks received", result);
}
// Simulate touch sequence to send GestureTap to sub-frame.
SyntheticTapGestureParams params;
params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT;
gfx::Point center(150, 150);
params.position = gfx::PointF(center.x(), center.y());
params.duration_ms = 100;
std::unique_ptr<SyntheticTapGesture> gesture(new SyntheticTapGesture(params));
RenderWidgetHostImpl* render_widget_host =
root->current_frame_host()->GetRenderWidgetHost();
InputEventAckWaiter ack_waiter(child_frame_host->GetRenderWidgetHost(),
blink::WebInputEvent::kGestureTap);
#if defined(USE_AURA)
// Allows the gesture events to go through under mash.
SystemEventRewriter::ScopedAllow scoped_allow(&event_rewriter_);
#endif
render_widget_host->QueueSyntheticGesture(
std::move(gesture), base::BindOnce([](SyntheticGesture::Result result) {
EXPECT_EQ(SyntheticGesture::GESTURE_FINISHED, result);
}));
// We must wait for the kGestureTap ack to come back before querying the click
// handler in the subframe.
ack_waiter.Wait();
// Verify click handler in subframe was invoked
{
std::string result;
EXPECT_TRUE(ExecuteScriptAndExtractString(
child_frame_host,
"window.domAutomationController.send(getClickStatus());", &result));
EXPECT_EQ("1 click received", result);
}
}
namespace {
// Defined here to be close to
// SitePerProcessHitTestBrowserTest.InputEventRouterGestureTargetQueueTest.
// Will wait for RenderWidgetHost's compositor thread to sync if one is given.
// Returns the unique_touch_id of the TouchStart.
uint32_t SendTouchTapWithExpectedTarget(
RenderWidgetHostViewBase* root_view,
const gfx::Point& touch_point,
RenderWidgetHostViewBase*& router_touch_target,
RenderWidgetHostViewBase* expected_target,
RenderWidgetHostImpl* child_render_widget_host) {
auto* root_view_aura = static_cast<RenderWidgetHostViewAura*>(root_view);
if (child_render_widget_host != nullptr) {
MainThreadFrameObserver observer(child_render_widget_host);
observer.Wait();
}
ui::TouchEvent touch_event_pressed(
ui::ET_TOUCH_PRESSED, touch_point, ui::EventTimeForNow(),
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH,
/* pointer_id*/ 0,
/* radius_x */ 30.0f,
/* radius_y */ 30.0f,
/* force */ 0.0f));
UpdateEventRootLocation(&touch_event_pressed, root_view_aura);
InputEventAckWaiter waiter(expected_target->GetRenderWidgetHost(),
blink::WebInputEvent::kTouchStart);
root_view_aura->OnTouchEvent(&touch_event_pressed);
if (child_render_widget_host != nullptr) {
MainThreadFrameObserver observer(child_render_widget_host);
observer.Wait();
}
waiter.Wait();
EXPECT_EQ(expected_target, router_touch_target);
ui::TouchEvent touch_event_released(
ui::ET_TOUCH_RELEASED, touch_point, ui::EventTimeForNow(),
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH,
/* pointer_id*/ 0,
/* radius_x */ 30.0f,
/* radius_y */ 30.0f,
/* force */ 0.0f));
UpdateEventRootLocation(&touch_event_released, root_view_aura);
root_view_aura->OnTouchEvent(&touch_event_released);
if (child_render_widget_host != nullptr) {
MainThreadFrameObserver observer(child_render_widget_host);
observer.Wait();
}
EXPECT_EQ(nullptr, router_touch_target);
return touch_event_pressed.unique_event_id();
}
void SendGestureTapSequenceWithExpectedTarget(
RenderWidgetHostViewBase* root_view,
const gfx::Point& gesture_point,
RenderWidgetHostViewBase*& router_gesture_target,
const RenderWidgetHostViewBase* expected_target,
const uint32_t unique_touch_event_id) {
auto* root_view_aura = static_cast<RenderWidgetHostViewAura*>(root_view);
ui::GestureEventDetails gesture_begin_details(ui::ET_GESTURE_BEGIN);
gesture_begin_details.set_device_type(
ui::GestureDeviceType::DEVICE_TOUCHSCREEN);
ui::GestureEvent gesture_begin_event(
gesture_point.x(), gesture_point.y(), 0, ui::EventTimeForNow(),
gesture_begin_details, unique_touch_event_id);
UpdateEventRootLocation(&gesture_begin_event, root_view_aura);
root_view_aura->OnGestureEvent(&gesture_begin_event);
ui::GestureEventDetails gesture_tap_down_details(ui::ET_GESTURE_TAP_DOWN);
gesture_tap_down_details.set_device_type(
ui::GestureDeviceType::DEVICE_TOUCHSCREEN);
ui::GestureEvent gesture_tap_down_event(
gesture_point.x(), gesture_point.y(), 0, ui::EventTimeForNow(),
gesture_tap_down_details, unique_touch_event_id);
UpdateEventRootLocation(&gesture_tap_down_event, root_view_aura);
root_view_aura->OnGestureEvent(&gesture_tap_down_event);
EXPECT_EQ(expected_target, router_gesture_target);
ui::GestureEventDetails gesture_show_press_details(ui::ET_GESTURE_SHOW_PRESS);
gesture_show_press_details.set_device_type(
ui::GestureDeviceType::DEVICE_TOUCHSCREEN);
ui::GestureEvent gesture_show_press_event(
gesture_point.x(), gesture_point.y(), 0, ui::EventTimeForNow(),
gesture_show_press_details, unique_touch_event_id);
UpdateEventRootLocation(&gesture_show_press_event, root_view_aura);
root_view_aura->OnGestureEvent(&gesture_show_press_event);
EXPECT_EQ(expected_target, router_gesture_target);
ui::GestureEventDetails gesture_tap_details(ui::ET_GESTURE_TAP);
gesture_tap_details.set_device_type(
ui::GestureDeviceType::DEVICE_TOUCHSCREEN);
gesture_tap_details.set_tap_count(1);
ui::GestureEvent gesture_tap_event(gesture_point.x(), gesture_point.y(), 0,
ui::EventTimeForNow(), gesture_tap_details,
unique_touch_event_id);
UpdateEventRootLocation(&gesture_tap_event, root_view_aura);
root_view_aura->OnGestureEvent(&gesture_tap_event);
EXPECT_EQ(nullptr, router_gesture_target);
ui::GestureEventDetails gesture_end_details(ui::ET_GESTURE_END);
gesture_end_details.set_device_type(
ui::GestureDeviceType::DEVICE_TOUCHSCREEN);
ui::GestureEvent gesture_end_event(gesture_point.x(), gesture_point.y(), 0,
ui::EventTimeForNow(), gesture_end_details,
unique_touch_event_id);
UpdateEventRootLocation(&gesture_end_event, root_view_aura);
root_view_aura->OnGestureEvent(&gesture_end_event);
EXPECT_EQ(nullptr, router_gesture_target);
}
void SendTouchpadPinchSequenceWithExpectedTarget(
RenderWidgetHostViewBase* root_view,
const gfx::Point& gesture_point,
RenderWidgetHostViewBase*& router_touchpad_gesture_target,
RenderWidgetHostViewBase* expected_target) {
auto* root_view_aura = static_cast<RenderWidgetHostViewAura*>(root_view);
ui::GestureEventDetails pinch_begin_details(ui::ET_GESTURE_PINCH_BEGIN);
pinch_begin_details.set_device_type(ui::GestureDeviceType::DEVICE_TOUCHPAD);
ui::GestureEvent pinch_begin(gesture_point.x(), gesture_point.y(), 0,
ui::EventTimeForNow(), pinch_begin_details);
UpdateEventRootLocation(&pinch_begin, root_view_aura);
TestInputEventObserver target_monitor(expected_target->GetRenderWidgetHost());
InputEventAckWaiter waiter(expected_target->GetRenderWidgetHost(),
blink::WebInputEvent::kGesturePinchBegin);
root_view_aura->OnGestureEvent(&pinch_begin);
// If the expected target is not the root, then we should be doing async
// targeting first. So event dispatch should not happen synchronously.
// Validate that the expected target does not receive the event immediately in
// such cases.
// V2 surface layer hit testing cannot handle pointer-events: none elements
// yet, see https://crbug.com/841358.
if (root_view != expected_target &&
!features::IsVizHitTestingSurfaceLayerEnabled()) {
EXPECT_FALSE(target_monitor.EventWasReceived());
}
waiter.Wait();
EXPECT_TRUE(target_monitor.EventWasReceived());
EXPECT_EQ(expected_target, router_touchpad_gesture_target);
target_monitor.ResetEventsReceived();
ui::GestureEventDetails pinch_update_details(ui::ET_GESTURE_PINCH_UPDATE);
pinch_update_details.set_device_type(ui::GestureDeviceType::DEVICE_TOUCHPAD);
pinch_update_details.set_scale(1.23);
ui::GestureEvent pinch_update(gesture_point.x(), gesture_point.y(), 0,
ui::EventTimeForNow(), pinch_update_details);
UpdateEventRootLocation(&pinch_update, root_view_aura);
root_view_aura->OnGestureEvent(&pinch_update);
EXPECT_EQ(expected_target, router_touchpad_gesture_target);
EXPECT_TRUE(target_monitor.EventWasReceived());
EXPECT_EQ(target_monitor.EventType(),
blink::WebInputEvent::kGesturePinchUpdate);
target_monitor.ResetEventsReceived();
ui::GestureEventDetails pinch_end_details(ui::ET_GESTURE_PINCH_END);
pinch_end_details.set_device_type(ui::GestureDeviceType::DEVICE_TOUCHPAD);
ui::GestureEvent pinch_end(gesture_point.x(), gesture_point.y(), 0,
ui::EventTimeForNow(), pinch_end_details);
UpdateEventRootLocation(&pinch_end, root_view_aura);
root_view_aura->OnGestureEvent(&pinch_end);
EXPECT_TRUE(target_monitor.EventWasReceived());
EXPECT_EQ(target_monitor.EventType(), blink::WebInputEvent::kGesturePinchEnd);
EXPECT_EQ(nullptr, router_touchpad_gesture_target);
}
#if !defined(OS_WIN)
// Sending touchpad fling events is not supported on Windows.
void SendTouchpadFlingSequenceWithExpectedTarget(
RenderWidgetHostViewBase* root_view,
const gfx::Point& gesture_point,
RenderWidgetHostViewBase*& router_wheel_target,
RenderWidgetHostViewBase* expected_target) {
auto* root_view_aura = static_cast<RenderWidgetHostViewAura*>(root_view);
ui::ScrollEvent scroll_begin(ui::ET_SCROLL, gesture_point,
ui::EventTimeForNow(), 0, 1, 0, 1, 0, 2);
UpdateEventRootLocation(&scroll_begin, root_view_aura);
root_view_aura->OnScrollEvent(&scroll_begin);
ui::ScrollEvent fling_start(ui::ET_SCROLL_FLING_START, gesture_point,
ui::EventTimeForNow(), 0, 1, 0, 1, 0, 1);
UpdateEventRootLocation(&fling_start, root_view_aura);
TestInputEventObserver target_monitor(expected_target->GetRenderWidgetHost());
InputEventAckWaiter fling_start_waiter(
expected_target->GetRenderWidgetHost(),
blink::WebInputEvent::kGestureFlingStart);
InputMsgWatcher gestrue_scroll_end_waiter(
expected_target->GetRenderWidgetHost(),
blink::WebInputEvent::kGestureScrollEnd);
root_view_aura->OnScrollEvent(&fling_start);
// If the expected target is not the root, then we should be doing async
// targeting first. So event dispatch should not happen synchronously.
// Validate that the expected target does not receive the event immediately in
// such cases.
// When V2 surface layer hit testing is enabled, we should synchronously
// target the event to the child.
if (root_view != expected_target &&
!features::IsVizHitTestingSurfaceLayerEnabled()) {
EXPECT_FALSE(target_monitor.EventWasReceived());
}
fling_start_waiter.Wait();
EXPECT_TRUE(target_monitor.EventWasReceived());
EXPECT_EQ(expected_target, router_wheel_target);
target_monitor.ResetEventsReceived();
// Send a GFC event, the fling_controller will process the GFC and stop the
// fling by generating a wheel event with phaseEnded. The
// mouse_wheel_event_queue will process the wheel event and generate a GSE.
InputEventAckWaiter fling_cancel_waiter(
expected_target->GetRenderWidgetHost(),
blink::WebInputEvent::kGestureFlingCancel);
ui::ScrollEvent fling_cancel(ui::ET_SCROLL_FLING_CANCEL, gesture_point,
ui::EventTimeForNow(), 0, 1, 0, 1, 0, 1);
UpdateEventRootLocation(&fling_cancel, root_view_aura);
root_view_aura->OnScrollEvent(&fling_cancel);
// Since the fling velocity is small, sometimes the fling is over before
// sending the GFC event.
gestrue_scroll_end_waiter.GetAckStateWaitIfNecessary();
fling_cancel_waiter.Wait();
}
#endif // !defined(OS_WIN)
} // anonymous namespace
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
InputEventRouterGestureTargetMapTest) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_nested_frames.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
WebContentsImpl* contents = web_contents();
FrameTreeNode* root = contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
GURL frame_url(
embedded_test_server()->GetURL("b.com", "/page_with_click_handler.html"));
NavigateFrameToURL(root->child_at(0), frame_url);
auto* child_frame_host = root->child_at(0)->current_frame_host();
auto* rwhv_child =
static_cast<RenderWidgetHostViewBase*>(child_frame_host->GetView());
// Synchronize with the child and parent renderers to guarantee that the
// surface information required for event hit testing is ready.
WaitForHitTestDataOrChildSurfaceReady(child_frame_host);
// All touches & gestures are sent to the main frame's view, and should be
// routed appropriately from there.
auto* rwhv_parent = static_cast<RenderWidgetHostViewBase*>(
contents->GetRenderWidgetHostView());
RenderWidgetHostInputEventRouter* router = contents->GetInputEventRouter();
EXPECT_TRUE(router->touchscreen_gesture_target_map_.empty());
EXPECT_EQ(nullptr, router->touchscreen_gesture_target_);
// Send touch sequence to main-frame.
gfx::Point main_frame_point(25, 25);
uint32_t firstId = SendTouchTapWithExpectedTarget(
rwhv_parent, main_frame_point, router->touch_target_, rwhv_parent,
nullptr);
EXPECT_EQ(1u, router->touchscreen_gesture_target_map_.size());
EXPECT_EQ(nullptr, router->touchscreen_gesture_target_);
// Send touch sequence to child.
gfx::Point child_center(150, 150);
uint32_t secondId = SendTouchTapWithExpectedTarget(
rwhv_parent, child_center, router->touch_target_, rwhv_child, nullptr);
EXPECT_EQ(2u, router->touchscreen_gesture_target_map_.size());
EXPECT_EQ(nullptr, router->touchscreen_gesture_target_);
// Send another touch sequence to main frame.
uint32_t thirdId = SendTouchTapWithExpectedTarget(
rwhv_parent, main_frame_point, router->touch_target_, rwhv_parent,
nullptr);
EXPECT_EQ(3u, router->touchscreen_gesture_target_map_.size());
EXPECT_EQ(nullptr, router->touchscreen_gesture_target_);
// Send Gestures to clear GestureTargetQueue.
// The first touch sequence should generate a GestureTapDown, sent to the
// main frame.
SendGestureTapSequenceWithExpectedTarget(rwhv_parent, main_frame_point,
router->touchscreen_gesture_target_,
rwhv_parent, firstId);
EXPECT_EQ(2u, router->touchscreen_gesture_target_map_.size());
// The second touch sequence should generate a GestureTapDown, sent to the
// child frame.
SendGestureTapSequenceWithExpectedTarget(rwhv_parent, child_center,
router->touchscreen_gesture_target_,
rwhv_child, secondId);
EXPECT_EQ(1u, router->touchscreen_gesture_target_map_.size());
// The third touch sequence should generate a GestureTapDown, sent to the
// main frame.
SendGestureTapSequenceWithExpectedTarget(rwhv_parent, main_frame_point,
router->touchscreen_gesture_target_,
rwhv_parent, thirdId);
EXPECT_EQ(0u, router->touchscreen_gesture_target_map_.size());
}
// TODO: Flaking test crbug.com/802827
#if defined(OS_WIN)
#define MAYBE_InputEventRouterGesturePreventDefaultTargetMapTest \
DISABLED_InputEventRouterGesturePreventDefaultTargetMapTest
#else
#define MAYBE_InputEventRouterGesturePreventDefaultTargetMapTest \
InputEventRouterGesturePreventDefaultTargetMapTest
#endif
#if defined(USE_AURA) || defined(OS_ANDROID)
IN_PROC_BROWSER_TEST_P(
SitePerProcessHitTestBrowserTest,
MAYBE_InputEventRouterGesturePreventDefaultTargetMapTest) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_nested_frames.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
WebContentsImpl* contents = web_contents();
FrameTreeNode* root = contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
GURL frame_url(embedded_test_server()->GetURL(
"b.com", "/page_with_touch_start_default_prevented.html"));
NavigateFrameToURL(root->child_at(0), frame_url);
auto* child_frame_host = root->child_at(0)->current_frame_host();
RenderWidgetHostImpl* child_render_widget_host =
child_frame_host->GetRenderWidgetHost();
auto* rwhv_child =
static_cast<RenderWidgetHostViewBase*>(child_frame_host->GetView());
// Synchronize with the child and parent renderers to guarantee that the
// surface information required for event hit testing is ready.
WaitForHitTestDataOrChildSurfaceReady(child_frame_host);
// All touches & gestures are sent to the main frame's view, and should be
// routed appropriately from there.
auto* rwhv_parent = static_cast<RenderWidgetHostViewBase*>(
contents->GetRenderWidgetHostView());
RenderWidgetHostInputEventRouter* router = contents->GetInputEventRouter();
EXPECT_TRUE(router->touchscreen_gesture_target_map_.empty());
EXPECT_EQ(nullptr, router->touchscreen_gesture_target_);
// Send touch sequence to main-frame.
gfx::Point main_frame_point(25, 25);
uint32_t firstId = SendTouchTapWithExpectedTarget(
rwhv_parent, main_frame_point, router->touch_target_, rwhv_parent,
child_render_widget_host);
EXPECT_EQ(1u, router->touchscreen_gesture_target_map_.size());
EXPECT_EQ(nullptr, router->touchscreen_gesture_target_);
// Send touch sequence to child.
gfx::Point child_center(150, 150);
SendTouchTapWithExpectedTarget(rwhv_parent, child_center,
router->touch_target_, rwhv_child,
child_render_widget_host);
EXPECT_EQ(1u, router->touchscreen_gesture_target_map_.size());
EXPECT_EQ(nullptr, router->touchscreen_gesture_target_);
// Send another touch sequence to main frame.
uint32_t thirdId = SendTouchTapWithExpectedTarget(
rwhv_parent, main_frame_point, router->touch_target_, rwhv_parent,
child_render_widget_host);
EXPECT_EQ(2u, router->touchscreen_gesture_target_map_.size());
EXPECT_EQ(nullptr, router->touchscreen_gesture_target_);
// Send Gestures to clear GestureTargetQueue.
// The first touch sequence should generate a GestureTapDown, sent to the
// main frame.
SendGestureTapSequenceWithExpectedTarget(rwhv_parent, main_frame_point,
router->touchscreen_gesture_target_,
rwhv_parent, firstId);
EXPECT_EQ(1u, router->touchscreen_gesture_target_map_.size());
// The third touch sequence should generate a GestureTapDown, sent to the
// main frame.
SendGestureTapSequenceWithExpectedTarget(rwhv_parent, main_frame_point,
router->touchscreen_gesture_target_,
rwhv_parent, thirdId);
EXPECT_EQ(0u, router->touchscreen_gesture_target_map_.size());
}
#endif // defined(USE_AURA) || defined(OS_ANDROID)
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
InputEventRouterTouchpadGestureTargetTest) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_nested_frames.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
WebContentsImpl* contents = web_contents();
FrameTreeNode* root = contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
GURL frame_url(
embedded_test_server()->GetURL("b.com", "/page_with_click_handler.html"));
NavigateFrameToURL(root->child_at(0), frame_url);
auto* child_frame_host = root->child_at(0)->current_frame_host();
// Synchronize with the child and parent renderers to guarantee that the
// surface information required for event hit testing is ready.
auto* rwhv_child =
static_cast<RenderWidgetHostViewBase*>(child_frame_host->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_frame_host);
// All touches & gestures are sent to the main frame's view, and should be
// routed appropriately from there.
auto* rwhv_parent = static_cast<RenderWidgetHostViewBase*>(
contents->GetRenderWidgetHostView());
RenderWidgetHostInputEventRouter* router = contents->GetInputEventRouter();
EXPECT_EQ(nullptr, router->touchpad_gesture_target_);
// TODO(848050): If we send multiple touchpad pinch sequences to separate
// views and the timing of the acks are such that the begin ack of the second
// sequence arrives in the root before the end ack of the first sequence, we
// would produce an invalid gesture event sequence. For now, we wait for the
// root to receive the end ack before sending a pinch sequence to a different
// view. The root view should preserve validity of input event sequences
// when processing acks from multiple views, so that waiting here is not
// necessary.
auto wait_for_pinch_sequence_end = base::BindRepeating(
[](RenderWidgetHost* rwh) {
InputEventAckWaiter pinch_end_observer(
rwh, base::BindRepeating([](content::InputEventAckSource,
content::InputEventAckState,
const blink::WebInputEvent& event) {
return event.GetType() ==
blink::WebGestureEvent::kGesturePinchEnd &&
!static_cast<const blink::WebGestureEvent&>(event)
.NeedsWheelEvent();
}));
pinch_end_observer.Wait();
},
rwhv_parent->GetRenderWidgetHost());
gfx::Point main_frame_point(25, 25);
gfx::Point child_center(150, 150);
// Send touchpad pinch sequence to main-frame.
SendTouchpadPinchSequenceWithExpectedTarget(rwhv_parent, main_frame_point,
router->touchpad_gesture_target_,
rwhv_parent);
wait_for_pinch_sequence_end.Run();
// Send touchpad pinch sequence to child.
SendTouchpadPinchSequenceWithExpectedTarget(
rwhv_parent, child_center, router->touchpad_gesture_target_, rwhv_child);
wait_for_pinch_sequence_end.Run();
// Send another touchpad pinch sequence to main frame.
SendTouchpadPinchSequenceWithExpectedTarget(rwhv_parent, main_frame_point,
router->touchpad_gesture_target_,
rwhv_parent);
#if !defined(OS_WIN)
// Sending touchpad fling events is not supported on Windows.
// Send touchpad fling sequence to main-frame.
SendTouchpadFlingSequenceWithExpectedTarget(
rwhv_parent, main_frame_point, router->wheel_target_, rwhv_parent);
// Send touchpad fling sequence to child.
SendTouchpadFlingSequenceWithExpectedTarget(
rwhv_parent, child_center, router->wheel_target_, rwhv_child);
// Send another touchpad fling sequence to main frame.
SendTouchpadFlingSequenceWithExpectedTarget(
rwhv_parent, main_frame_point, router->wheel_target_, rwhv_parent);
#endif
}
// Test that performing a touchpad pinch over an OOPIF offers the synthetic
// wheel events to the child and causes the page scale factor to change for
// the main frame (given that the child did not consume the wheel).
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
TouchpadPinchOverOOPIF) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
RenderFrameSubmissionObserver render_frame_submission_observer(
shell()->web_contents());
WebContentsImpl* contents = web_contents();
FrameTreeNode* root = contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
GURL frame_url(
embedded_test_server()->GetURL("b.com", "/page_with_wheel_handler.html"));
NavigateFrameToURL(root->child_at(0), frame_url);
auto* child_frame_host = root->child_at(0)->current_frame_host();
auto* rwhv_child =
static_cast<RenderWidgetHostViewBase*>(child_frame_host->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_frame_host);
auto* rwhv_parent = static_cast<RenderWidgetHostViewBase*>(
contents->GetRenderWidgetHostView());
RenderWidgetHostInputEventRouter* router = contents->GetInputEventRouter();
EXPECT_EQ(nullptr, router->touchpad_gesture_target_);
const float scale_factor =
render_frame_submission_observer.LastRenderFrameMetadata()
.page_scale_factor;
const gfx::Point point_in_child(gfx::ToCeiledInt(100 * scale_factor),
gfx::ToCeiledInt(100 * scale_factor));
content::TestPageScaleObserver scale_observer(shell()->web_contents());
SendTouchpadPinchSequenceWithExpectedTarget(rwhv_parent, point_in_child,
router->touchpad_gesture_target_,
rwhv_child);
// Ensure the child frame saw the wheel event.
bool default_prevented = false;
ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
child_frame_host,
"handlerPromise.then(function(e) {"
" window.domAutomationController.send(e.defaultPrevented);"
"});",
&default_prevented));
EXPECT_FALSE(default_prevented);
scale_observer.WaitForPageScaleUpdate();
}
#endif // defined(USE_AURA)
// Test that we can still perform a touchpad pinch gesture in the absence of viz
// hit test data without crashing.
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
TouchpadPinchWhenMissingHitTestDataDoesNotCrash) {
if (!features::IsVizHitTestingEnabled())
return;
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/frame_tree/page_with_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
WebContentsImpl* contents = web_contents();
FrameTreeNode* root = contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
// Even though we're sending the events to the root, we need an OOPIF so
// that hit testing doesn't short circuit.
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://baz.com/",
DepictFrameTree(root));
// Clobber the real hit test data once it comes in.
WaitForHitTestDataOrChildSurfaceReady(root->current_frame_host());
ASSERT_TRUE(GetHostFrameSinkManager());
viz::HostFrameSinkManager::DisplayHitTestQueryMap empty_hit_test_map;
viz::HostFrameSinkManagerTestApi(GetHostFrameSinkManager())
.SetDisplayHitTestQuery(std::move(empty_hit_test_map));
const gfx::PointF point_in_root(1, 1);
SyntheticPinchGestureParams params;
params.gesture_source_type = SyntheticGestureParams::TOUCHPAD_INPUT;
params.scale_factor = 1.2f;
params.anchor = point_in_root;
auto pinch_gesture = std::make_unique<SyntheticTouchpadPinchGesture>(params);
RenderWidgetHostImpl* render_widget_host =
root->current_frame_host()->GetRenderWidgetHost();
base::RunLoop run_loop;
render_widget_host->QueueSyntheticGesture(
std::move(pinch_gesture),
base::BindOnce(
[](base::OnceClosure quit_closure, SyntheticGesture::Result result) {
std::move(quit_closure).Run();
},
run_loop.QuitClosure()));
run_loop.Run();
}
// Tests that performing a touchpad double-tap zoom over an OOPIF offers the
// synthetic wheel event to the child.
#if defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_WIN)
// Flaky on mac, linux and win. crbug.com/947193
#define MAYBE_TouchpadDoubleTapZoomOverOOPIF \
DISABLED_TouchpadDoubleTapZoomOverOOPIF
#else
#define MAYBE_TouchpadDoubleTapZoomOverOOPIF TouchpadDoubleTapZoomOverOOPIF
#endif
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
MAYBE_TouchpadDoubleTapZoomOverOOPIF) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
WebContentsImpl* contents = web_contents();
WebPreferences prefs = contents->GetRenderViewHost()->GetWebkitPreferences();
prefs.double_tap_to_zoom_enabled = true;
contents->GetRenderViewHost()->UpdateWebkitPreferences(prefs);
RenderFrameSubmissionObserver render_frame_submission_observer(
shell()->web_contents());
FrameTreeNode* root = contents->GetFrameTree()->root();
ASSERT_EQ(1U, root->child_count());
GURL frame_url(
embedded_test_server()->GetURL("b.com", "/page_with_wheel_handler.html"));
NavigateFrameToURL(root->child_at(0), frame_url);
auto* child_frame_host = root->child_at(0)->current_frame_host();
WaitForHitTestDataOrChildSurfaceReady(child_frame_host);
auto* root_view = static_cast<RenderWidgetHostViewBase*>(
contents->GetRenderWidgetHostView());
RenderWidgetHostViewBase* child_view = static_cast<RenderWidgetHostViewBase*>(
child_frame_host->GetRenderWidgetHost()->GetView());
const float scale_factor =
render_frame_submission_observer.LastRenderFrameMetadata()
.page_scale_factor;
gfx::PointF point_in_screen(child_view->GetViewBounds().CenterPoint());
point_in_screen.Scale(scale_factor);
// It might seem weird to not also scale the root_view's view bounds, but
// since the origin should be unaffected by page scale we don't need to.
const gfx::PointF root_location(
point_in_screen - root_view->GetViewBounds().OffsetFromOrigin());
RenderWidgetHostInputEventRouter* router = contents->GetInputEventRouter();
blink::WebGestureEvent double_tap_zoom(
blink::WebInputEvent::kGestureDoubleTap,
blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests(),
blink::WebGestureDevice::kTouchpad);
double_tap_zoom.SetPositionInWidget(root_location);
double_tap_zoom.SetPositionInScreen(point_in_screen);
double_tap_zoom.data.tap.tap_count = 1;
double_tap_zoom.SetNeedsWheelEvent(true);
content::TestPageScaleObserver scale_observer(shell()->web_contents());
router->RouteGestureEvent(root_view, &double_tap_zoom,
ui::LatencyInfo(ui::SourceEventType::WHEEL));
// Ensure the child frame saw the wheel event.
bool default_prevented = false;
ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
child_frame_host,
"handlerPromise.then(function(e) {"
" window.domAutomationController.send(e.defaultPrevented);"
"});",
&default_prevented));
EXPECT_FALSE(default_prevented);
// TODO(mcnee): Support double-tap zoom gesture for OOPIFs. For now, we
// only test that any scale change still happens in the main frame when
// the double tap is performed over the OOPIF. Once this works with OOPIFs,
// we should be able to test that the new scale is based on the target
// rect of the element in the OOPIF. https://crbug.com/758348
scale_observer.WaitForPageScaleUpdate();
}
// A WebContentsDelegate to capture ContextMenu creation events.
class ContextMenuObserverDelegate : public WebContentsDelegate {
public:
ContextMenuObserverDelegate()
: context_menu_created_(false),
message_loop_runner_(new MessageLoopRunner) {}
~ContextMenuObserverDelegate() override {}
bool HandleContextMenu(content::RenderFrameHost* render_frame_host,
const content::ContextMenuParams& params) override {
context_menu_created_ = true;
menu_params_ = params;
message_loop_runner_->Quit();
return true;
}
ContextMenuParams getParams() { return menu_params_; }
void Wait() {
if (!context_menu_created_)
message_loop_runner_->Run();
context_menu_created_ = false;
}
private:
bool context_menu_created_;
ContextMenuParams menu_params_;
// The MessageLoopRunner used to spin the message loop.
scoped_refptr<MessageLoopRunner> message_loop_runner_;
DISALLOW_COPY_AND_ASSIGN(ContextMenuObserverDelegate);
};
// Helper function to run the CreateContextMenuTest in either normal
// or high DPI mode.
void CreateContextMenuTestHelper(
Shell* shell,
net::test_server::EmbeddedTestServer* embedded_test_server) {
GURL main_url(embedded_test_server->GetURL(
"/frame_tree/page_with_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell, main_url));
RenderFrameSubmissionObserver render_frame_submission_observer(
shell->web_contents());
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server->GetURL("baz.com", "/title1.html"));
EXPECT_EQ(site_url, child_node->current_url());
EXPECT_NE(shell->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
// Ensure that the child process renderer is ready to have input events
// routed to it. This happens when the browser process has received
// updated compositor surfaces from both renderer processes.
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
// A WebContentsDelegate to listen for the ShowContextMenu message.
ContextMenuObserverDelegate context_menu_delegate;
shell->web_contents()->SetDelegate(&context_menu_delegate);
RenderWidgetHostInputEventRouter* router =
static_cast<WebContentsImpl*>(shell->web_contents())
->GetInputEventRouter();
float scale_factor =
render_frame_submission_observer.LastRenderFrameMetadata()
.page_scale_factor;
gfx::Rect root_bounds = root_view->GetViewBounds();
gfx::Rect bounds = rwhv_child->GetViewBounds();
gfx::Point point(
gfx::ToCeiledInt((bounds.x() - root_bounds.x() + 5) * scale_factor),
gfx::ToCeiledInt((bounds.y() - root_bounds.y() + 5) * scale_factor));
// Target right-click event to child frame.
blink::WebMouseEvent click_event(
blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
click_event.button = blink::WebPointerProperties::Button::kRight;
SetWebEventPositions(&click_event, point, root_view);
click_event.click_count = 1;
router->RouteMouseEvent(root_view, &click_event, ui::LatencyInfo());
// We also need a MouseUp event, needed by Windows.
click_event.SetType(blink::WebInputEvent::kMouseUp);
SetWebEventPositions(&click_event, point, root_view);
router->RouteMouseEvent(root_view, &click_event, ui::LatencyInfo());
context_menu_delegate.Wait();
ContextMenuParams params = context_menu_delegate.getParams();
EXPECT_NEAR(point.x(), params.x, kHitTestTolerance);
EXPECT_NEAR(point.y(), params.y, kHitTestTolerance);
}
#if defined(OS_ANDROID) || defined(OS_WIN)
// High DPI tests don't work properly on Android, which has fixed scale factor.
// Windows is disabled because of https://crbug.com/545547.
#define MAYBE_CreateContextMenuTest DISABLED_CreateContextMenuTest
#else
#define MAYBE_CreateContextMenuTest CreateContextMenuTest
#endif
// Test that a mouse right-click to an out-of-process iframe causes a context
// menu to be generated with the correct screen position.
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
MAYBE_CreateContextMenuTest) {
CreateContextMenuTestHelper(shell(), embedded_test_server());
}
// Test that a mouse right-click to an out-of-process iframe causes a context
// menu to be generated with the correct screen position on a screen with
// non-default scale factor.
IN_PROC_BROWSER_TEST_P(SitePerProcessHighDPIHitTestBrowserTest,
MAYBE_CreateContextMenuTest) {
CreateContextMenuTestHelper(shell(), embedded_test_server());
}
// Test that clicking a select element in an out-of-process iframe creates
// a popup menu in the correct position.
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, PopupMenuTest) {
GURL main_url(
embedded_test_server()->GetURL("/cross_site_iframe_factory.html?a(a)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
FrameTreeNode* child_node = root->child_at(0);
GURL site_url(embedded_test_server()->GetURL(
"baz.com", "/site_isolation/page-with-select.html"));
NavigateFrameToURL(child_node, site_url);
web_contents()->SendScreenRects();
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
child_node->current_frame_host()->GetSiteInstance());
scoped_refptr<ShowWidgetMessageFilter> filter = new ShowWidgetMessageFilter();
child_node->current_frame_host()->GetProcess()->AddFilter(filter.get());
// Target left-click event to child frame.
blink::WebMouseEvent click_event(
blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
click_event.button = blink::WebPointerProperties::Button::kLeft;
SetWebEventPositions(&click_event, gfx::Point(15, 15), rwhv_root);
click_event.click_count = 1;
rwhv_child->ProcessMouseEvent(click_event, ui::LatencyInfo());
// Dismiss the popup.
SetWebEventPositions(&click_event, gfx::Point(1, 1), rwhv_root);
rwhv_child->ProcessMouseEvent(click_event, ui::LatencyInfo());
filter->Wait();
gfx::Rect popup_rect = filter->last_initial_rect();
if (IsUseZoomForDSFEnabled()) {
ScreenInfo screen_info;
shell()->web_contents()->GetRenderWidgetHostView()->GetScreenInfo(
&screen_info);
popup_rect = gfx::ScaleToRoundedRect(popup_rect,
1 / screen_info.device_scale_factor);
}
#if defined(OS_MACOSX) || defined(OS_ANDROID)
// On Mac and Android we receive the coordinates before they are transformed,
// so they are still relative to the out-of-process iframe origin.
EXPECT_EQ(popup_rect.x(), 9);
EXPECT_EQ(popup_rect.y(), 9);
#else
EXPECT_EQ(popup_rect.x() - rwhv_root->GetViewBounds().x(), 354);
EXPECT_EQ(popup_rect.y() - rwhv_root->GetViewBounds().y(), 94);
#endif
#if defined(OS_LINUX)
// Verify click-and-drag selection of popups still works on Linux with
// OOPIFs enabled. This is only necessary to test on Aura because Mac and
// Android use native widgets. Windows does not support this as UI
// convention (it requires separate clicks to open the menu and select an
// option). See https://crbug.com/703191.
int process_id = child_node->current_frame_host()->GetProcess()->GetID();
filter->Reset();
RenderWidgetHostInputEventRouter* router =
static_cast<WebContentsImpl*>(shell()->web_contents())
->GetInputEventRouter();
// Re-open the select element.
SetWebEventPositions(&click_event, gfx::Point(360, 90), rwhv_root);
click_event.click_count = 1;
router->RouteMouseEvent(rwhv_root, &click_event, ui::LatencyInfo());
filter->Wait();
RenderWidgetHostViewAura* popup_view = static_cast<RenderWidgetHostViewAura*>(
RenderWidgetHost::FromID(process_id, filter->last_routing_id())
->GetView());
// The IO thread posts to ViewMsg_ShowWidget handlers in both the message
// filter above and the WebContents, which initializes the popup's view.
// It is possible for this code to execute before the WebContents handler,
// in which case OnMouseEvent would be called on an uninitialized RWHVA.
// This loop ensures that the initialization completes before proceeding.
while (!popup_view->window()) {
base::RunLoop loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
loop.QuitClosure());
loop.Run();
}
RenderWidgetHostMouseEventMonitor popup_monitor(
popup_view->GetRenderWidgetHost());
// Next send a mouse up directly targeting the first option, simulating a
// drag. This requires a ui::MouseEvent because it tests behavior that is
// above RWH input event routing.
ui::MouseEvent mouse_up_event(ui::ET_MOUSE_RELEASED, gfx::Point(10, 5),
gfx::Point(10, 5), ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON,
ui::EF_LEFT_MOUSE_BUTTON);
UpdateEventRootLocation(&mouse_up_event, rwhv_root);
popup_view->OnMouseEvent(&mouse_up_event);
// This verifies that the popup actually received the event, and it wasn't
// diverted to a different RenderWidgetHostView due to mouse capture.
EXPECT_TRUE(popup_monitor.EventWasReceived());
#endif // defined(OS_LINUX)
}
// Test that clicking a select element in a nested out-of-process iframe creates
// a popup menu in the correct position, even if the top-level page repositions
// its out-of-process iframe. This verifies that screen positioning information
// is propagating down the frame tree correctly.
#if defined(OS_ANDROID)
// Surface-based hit testing and coordinate translation is not yet avaiable on
// Android.
#define MAYBE_NestedPopupMenuTest DISABLED_NestedPopupMenuTest
#else
// Times out frequently. https://crbug.com/599730.
#define MAYBE_NestedPopupMenuTest DISABLED_NestedPopupMenuTest
#endif
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
MAYBE_NestedPopupMenuTest) {
GURL main_url(embedded_test_server()->GetURL(
"/cross_site_iframe_factory.html?a(b(c))"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
web_contents()->SendScreenRects();
// For clarity, we are labeling the frame tree nodes as:
// - root_node
// \-> b_node (out-of-process from root and c_node)
// \-> c_node (out-of-process from root and b_node)
content::TestNavigationObserver navigation_observer(shell()->web_contents());
FrameTreeNode* b_node = root->child_at(0);
FrameTreeNode* c_node = b_node->child_at(0);
GURL site_url(embedded_test_server()->GetURL(
"baz.com", "/site_isolation/page-with-select.html"));
NavigateFrameToURL(c_node, site_url);
RenderWidgetHostViewBase* rwhv_c_node =
static_cast<RenderWidgetHostViewBase*>(
c_node->current_frame_host()->GetRenderWidgetHost()->GetView());
EXPECT_NE(shell()->web_contents()->GetSiteInstance(),
c_node->current_frame_host()->GetSiteInstance());
scoped_refptr<ShowWidgetMessageFilter> filter = new ShowWidgetMessageFilter();
c_node->current_frame_host()->GetProcess()->AddFilter(filter.get());
// Target left-click event to child frame.
blink::WebMouseEvent click_event(
blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
click_event.button = blink::WebPointerProperties::Button::kLeft;
SetWebEventPositions(&click_event, gfx::Point(15, 15), rwhv_root);
click_event.click_count = 1;
rwhv_c_node->ProcessMouseEvent(click_event, ui::LatencyInfo());
// Prompt the WebContents to dismiss the popup by clicking elsewhere.
SetWebEventPositions(&click_event, gfx::Point(1, 1), rwhv_root);
rwhv_c_node->ProcessMouseEvent(click_event, ui::LatencyInfo());
filter->Wait();
gfx::Rect popup_rect = filter->last_initial_rect();
#if defined(OS_MACOSX)
EXPECT_EQ(popup_rect.x(), 9);
EXPECT_EQ(popup_rect.y(), 9);
#else
EXPECT_EQ(popup_rect.x() - rwhv_root->GetViewBounds().x(), 354);
EXPECT_EQ(popup_rect.y() - rwhv_root->GetViewBounds().y(), 154);
#endif
// Save the screen rect for b_node. Since it updates asynchronously from
// the script command that changes it, we need to wait for it to change
// before attempting to create the popup widget again.
gfx::Rect last_b_node_bounds_rect =
b_node->current_frame_host()->GetView()->GetViewBounds();
std::string script =
"var iframe = document.querySelector('iframe');"
"iframe.style.position = 'absolute';"
"iframe.style.left = 150;"
"iframe.style.top = 150;";
EXPECT_TRUE(ExecuteScript(root, script));
filter->Reset();
// Busy loop to wait for b_node's screen rect to get updated. There
// doesn't seem to be any better way to find out when this happens.
while (last_b_node_bounds_rect.x() ==
b_node->current_frame_host()->GetView()->GetViewBounds().x() &&
last_b_node_bounds_rect.y() ==
b_node->current_frame_host()->GetView()->GetViewBounds().y()) {
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
run_loop.Run();
}
click_event.button = blink::WebPointerProperties::Button::kLeft;
SetWebEventPositions(&click_event, gfx::Point(15, 15), rwhv_root);
click_event.click_count = 1;
rwhv_c_node->ProcessMouseEvent(click_event, ui::LatencyInfo());
SetWebEventPositions(&click_event, gfx::Point(1, 1), rwhv_root);
rwhv_c_node->ProcessMouseEvent(click_event, ui::LatencyInfo());
filter->Wait();
popup_rect = filter->last_initial_rect();
#if defined(OS_MACOSX)
EXPECT_EQ(popup_rect.x(), 9);
EXPECT_EQ(popup_rect.y(), 9);
#else
EXPECT_EQ(popup_rect.x() - rwhv_root->GetViewBounds().x(), 203);
EXPECT_EQ(popup_rect.y() - rwhv_root->GetViewBounds().y(), 248);
#endif
}
// Verify that scrolling the main frame correctly updates the position to
// a nested child frame. See issue https://crbug.com/878703 for more
// information.
// On Mac and Android, the reported menu coordinates are relative to the
// OOPIF, and its screen position is computed later, so this test isn't
// relevant on those platforms.
// TODO(crbug.com/889002): This test is flaky.
#if !defined(OS_ANDROID) && !defined(OS_MACOSX)
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest,
DISABLED_ScrolledNestedPopupMenuTest) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/frame_tree/page_with_tall_positioned_frame.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = web_contents()->GetFrameTree()->root();
FrameTreeNode* child_node = root->child_at(0);
GURL child_url(embedded_test_server()->GetURL(
"b.com", "/frame_tree/page_with_positioned_frame.html"));
NavigateFrameToURL(child_node, child_url);
FrameTreeNode* grandchild_node = child_node->child_at(0);
GURL grandchild_url(embedded_test_server()->GetURL(
"c.com", "/site_isolation/page-with-select.html"));
NavigateFrameToURL(grandchild_node, grandchild_url);
WaitForHitTestDataOrChildSurfaceReady(grandchild_node->current_frame_host());
EXPECT_EQ(
" Site A ------------ proxies for B C\n"
" +--Site B ------- proxies for A C\n"
" +--Site C -- proxies for A B\n"
"Where A = http://a.com/\n"
" B = http://b.com/\n"
" C = http://c.com/",
DepictFrameTree(root));
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_grandchild =
static_cast<RenderWidgetHostViewBase*>(
grandchild_node->current_frame_host()
->GetRenderWidgetHost()
->GetView());
scoped_refptr<ShowWidgetMessageFilter> filter = new ShowWidgetMessageFilter();
grandchild_node->current_frame_host()->GetProcess()->AddFilter(filter.get());
// Target left-click event to the select element in the innermost frame.
DispatchMouseEventAndWaitUntilDispatch(web_contents(), rwhv_grandchild,
gfx::PointF(15, 15), rwhv_grandchild,
gfx::PointF(15, 15));
// Prompt the WebContents to dismiss the popup by clicking elsewhere.
DispatchMouseEventAndWaitUntilDispatch(web_contents(), rwhv_grandchild,
gfx::PointF(2, 2), rwhv_grandchild,
gfx::PointF(2, 2));
filter->Wait();
// This test isn't verifying correctness of these coordinates, this is just
// to ensure that they change after scroll.
gfx::Rect unscrolled_popup_rect = filter->last_initial_rect();
gfx::Rect initial_grandchild_view_bounds = rwhv_grandchild->GetViewBounds();
// Scroll the main frame.
EXPECT_TRUE(ExecuteScript(root, "window.scrollTo(0, 20);"));
// Wait until the OOPIF positions have been updated in the browser process.
while (true) {
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
run_loop.Run();
if (initial_grandchild_view_bounds.y() ==
rwhv_grandchild->GetViewBounds().y() + 20)
break;
}
filter->Reset();
// This sends the message directly to the rwhv_grandchild, avoiding using
// the helper methods, to avert a race condition with the surfaces or
// HitTestRegions needing to update post-scroll. The event won't hit test
// correctly if it gets sent before a fresh compositor frame is received.
blink::WebMouseEvent down_event(
blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
down_event.button = blink::WebPointerProperties::Button::kLeft;
down_event.click_count = 1;
down_event.SetPositionInWidget(15, 15);
rwhv_grandchild->ProcessMouseEvent(down_event, ui::LatencyInfo());
// Dismiss the popup again. This time there is no need to worry about
// compositor frame updates because it is sufficient to send the click to
// the root frame.
DispatchMouseEventAndWaitUntilDispatch(web_contents(), rwhv_root,
gfx::PointF(1, 1), rwhv_root,
gfx::PointF(1, 1));
filter->Wait();
EXPECT_EQ(unscrolled_popup_rect.y(), filter->last_initial_rect().y() + 20);
}
#endif // !defined(OS_ANDROID)
#if defined(USE_AURA)
class SitePerProcessGestureHitTestBrowserTest
: public SitePerProcessHitTestBrowserTest {
public:
SitePerProcessGestureHitTestBrowserTest() {}
// This functions simulates a sequence of events that are typical of a
// gesture pinch at |position|. We need this since machinery in the event
// codepath will require GesturePinch* to be enclosed in
// GestureScrollBegin/End, and since RenderWidgetHostInputEventRouter needs
// both the preceding touch events, as well as GestureTapDown, in order to
// correctly target the subsequent gesture event stream. The minimum stream
// required to trigger the correct behaviours is represented here, but could
// be expanded to include additional events such as one or more
// GestureScrollUpdate and GesturePinchUpdate events.
void SendPinchBeginEndSequence(RenderWidgetHostViewAura* rwhva,
const gfx::Point& position,
RenderWidgetHost* expected_target_rwh) {
DCHECK(rwhva);
// Use full version of constructor with radius, angle and force since it
// will crash in the renderer otherwise.
ui::TouchEvent touch_pressed(
ui::ET_TOUCH_PRESSED, position, ui::EventTimeForNow(),
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH,
/* pointer_id*/ 0,
/* radius_x */ 1.0f,
/* radius_y */ 1.0f,
/* force */ 1.0f));
UpdateEventRootLocation(&touch_pressed, rwhva);
InputEventAckWaiter waiter(expected_target_rwh,
blink::WebInputEvent::kTouchStart);
rwhva->OnTouchEvent(&touch_pressed);
waiter.Wait();
ui::GestureEventDetails gesture_tap_down_details(ui::ET_GESTURE_TAP_DOWN);
gesture_tap_down_details.set_device_type(
ui::GestureDeviceType::DEVICE_TOUCHSCREEN);
ui::GestureEvent gesture_tap_down(
position.x(), position.y(), 0, ui::EventTimeForNow(),
gesture_tap_down_details, touch_pressed.unique_event_id());
UpdateEventRootLocation(&gesture_tap_down, rwhva);
rwhva->OnGestureEvent(&gesture_tap_down);
ui::GestureEventDetails gesture_scroll_begin_details(
ui::ET_GESTURE_SCROLL_BEGIN);
gesture_scroll_begin_details.set_device_type(
ui::GestureDeviceType::DEVICE_TOUCHSCREEN);
gesture_scroll_begin_details.set_touch_points(2);
ui::GestureEvent gesture_scroll_begin(
position.x(), position.y(), 0, ui::EventTimeForNow(),
gesture_scroll_begin_details, touch_pressed.unique_event_id());
UpdateEventRootLocation(&gesture_scroll_begin, rwhva);
rwhva->OnGestureEvent(&gesture_scroll_begin);
ui::GestureEventDetails gesture_pinch_begin_details(
ui::ET_GESTURE_PINCH_BEGIN);
gesture_pinch_begin_details.set_device_type(
ui::GestureDeviceType::DEVICE_TOUCHSCREEN);
ui::GestureEvent gesture_pinch_begin(
position.x(), position.y(), 0, ui::EventTimeForNow(),
gesture_pinch_begin_details, touch_pressed.unique_event_id());
UpdateEventRootLocation(&gesture_pinch_begin, rwhva);
rwhva->OnGestureEvent(&gesture_pinch_begin);
ui::GestureEventDetails gesture_pinch_end_details(ui::ET_GESTURE_PINCH_END);
gesture_pinch_end_details.set_device_type(
ui::GestureDeviceType::DEVICE_TOUCHSCREEN);
ui::GestureEvent gesture_pinch_end(
position.x(), position.y(), 0, ui::EventTimeForNow(),
gesture_pinch_end_details, touch_pressed.unique_event_id());
UpdateEventRootLocation(&gesture_pinch_end, rwhva);
rwhva->OnGestureEvent(&gesture_pinch_end);
ui::GestureEventDetails gesture_scroll_end_details(
ui::ET_GESTURE_SCROLL_END);
gesture_scroll_end_details.set_device_type(
ui::GestureDeviceType::DEVICE_TOUCHSCREEN);
ui::GestureEvent gesture_scroll_end(
position.x(), position.y(), 0, ui::EventTimeForNow(),
gesture_scroll_end_details, touch_pressed.unique_event_id());
UpdateEventRootLocation(&gesture_scroll_end, rwhva);
rwhva->OnGestureEvent(&gesture_scroll_end);
// TouchActionFilter is reset when a touch event sequence ends, so in order
// to preserve the touch action set by TouchStart, we end release touch
// after pinch gestures.
ui::TouchEvent touch_released(
ui::ET_TOUCH_RELEASED, position, ui::EventTimeForNow(),
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH,
/* pointer_id*/ 0,
/* radius_x */ 1.0f,
/* radius_y */ 1.0f,
/* force */ 1.0f));
InputEventAckWaiter touch_released_waiter(expected_target_rwh,
blink::WebInputEvent::kTouchEnd);
rwhva->OnTouchEvent(&touch_released);
touch_released_waiter.Wait();
}
void SetupRootAndChild() {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root_node =
static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
FrameTreeNode* child_node = root_node->child_at(0);
rwhv_child_ = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
rwhva_root_ = static_cast<RenderWidgetHostViewAura*>(
shell()->web_contents()->GetRenderWidgetHostView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
rwhi_child_ = child_node->current_frame_host()->GetRenderWidgetHost();
rwhi_root_ = root_node->current_frame_host()->GetRenderWidgetHost();
}
void SubframeGesturePinchTestHelper(const std::string& url,
bool reset_root_touch_action) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root_node =
static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root_node->child_count());
FrameTreeNode* child_node = root_node->child_at(0);
GURL b_url(embedded_test_server()->GetURL("b.com", url));
NavigateFrameToURL(child_node, b_url);
ASSERT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://a.com/\n"
" B = http://b.com/",
DepictFrameTree(root_node));
rwhv_child_ = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
rwhva_root_ = static_cast<RenderWidgetHostViewAura*>(
shell()->web_contents()->GetRenderWidgetHostView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
MainThreadFrameObserver observer(rwhv_child_->GetRenderWidgetHost());
observer.Wait();
rwhi_child_ = child_node->current_frame_host()->GetRenderWidgetHost();
rwhi_root_ = root_node->current_frame_host()->GetRenderWidgetHost();
TestInputEventObserver root_frame_monitor(rwhi_root_);
TestInputEventObserver child_frame_monitor(rwhi_child_);
gfx::Rect bounds = rwhv_child_->GetViewBounds();
bounds.Offset(gfx::Point() - rwhva_root_->GetViewBounds().origin());
// The pinch gesture will always sent to the root frame even if the fingers
// are targeting the iframe. In this case, the test should not crash.
if (reset_root_touch_action) {
static_cast<InputRouterImpl*>(
static_cast<RenderWidgetHostImpl*>(rwhva_root_->GetRenderWidgetHost())
->input_router())
->ForceResetTouchActionForTest();
}
SendPinchBeginEndSequence(rwhva_root_, bounds.CenterPoint(), rwhi_child_);
if (reset_root_touch_action)
return;
// Verify that root-RWHI gets nothing.
EXPECT_FALSE(root_frame_monitor.EventWasReceived());
// Verify that child-RWHI gets TS/GTD/GSB/GPB/GPE/GSE/TE.
EXPECT_EQ(blink::WebInputEvent::kTouchStart,
child_frame_monitor.events_received()[0]);
EXPECT_EQ(blink::WebInputEvent::kGestureTapDown,
child_frame_monitor.events_received()[1]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollBegin,
child_frame_monitor.events_received()[2]);
EXPECT_EQ(blink::WebInputEvent::kGesturePinchBegin,
child_frame_monitor.events_received()[3]);
EXPECT_EQ(blink::WebInputEvent::kGesturePinchEnd,
child_frame_monitor.events_received()[4]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollEnd,
child_frame_monitor.events_received()[5]);
EXPECT_EQ(blink::WebInputEvent::kTouchEnd,
child_frame_monitor.events_received()[6]);
// Verify that the pinch gestures are consumed by browser.
EXPECT_EQ(InputEventAckSource::BROWSER,
child_frame_monitor.events_acked()[3]);
EXPECT_EQ(InputEventAckSource::BROWSER,
child_frame_monitor.events_acked()[4]);
}
protected:
RenderWidgetHostViewBase* rwhv_child_;
RenderWidgetHostViewAura* rwhva_root_;
RenderWidgetHostImpl* rwhi_child_;
RenderWidgetHostImpl* rwhi_root_;
private:
DISALLOW_COPY_AND_ASSIGN(SitePerProcessGestureHitTestBrowserTest);
};
IN_PROC_BROWSER_TEST_P(SitePerProcessGestureHitTestBrowserTest,
SubframeGesturePinchGoesToMainFrame) {
SetupRootAndChild();
TestInputEventObserver root_frame_monitor(rwhi_root_);
TestInputEventObserver child_frame_monitor(rwhi_child_);
// Need child rect in main frame coords.
gfx::Rect bounds = rwhv_child_->GetViewBounds();
bounds.Offset(gfx::Point() - rwhva_root_->GetViewBounds().origin());
SendPinchBeginEndSequence(rwhva_root_, bounds.CenterPoint(), rwhi_child_);
// Verify root-RWHI gets GSB/GPB/GPE/GSE.
EXPECT_TRUE(root_frame_monitor.EventWasReceived());
EXPECT_EQ(blink::WebInputEvent::kGestureScrollBegin,
root_frame_monitor.events_received()[0]);
EXPECT_EQ(blink::WebInputEvent::kGesturePinchBegin,
root_frame_monitor.events_received()[1]);
EXPECT_EQ(blink::WebInputEvent::kGesturePinchEnd,
root_frame_monitor.events_received()[2]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollEnd,
root_frame_monitor.events_received()[3]);
// Verify child-RWHI gets TS/TE, GTD/GSB/GSE.
EXPECT_TRUE(child_frame_monitor.EventWasReceived());
EXPECT_EQ(blink::WebInputEvent::kTouchStart,
child_frame_monitor.events_received()[0]);
EXPECT_EQ(blink::WebInputEvent::kGestureTapDown,
child_frame_monitor.events_received()[1]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollBegin,
child_frame_monitor.events_received()[2]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollEnd,
child_frame_monitor.events_received()[3]);
EXPECT_EQ(blink::WebInputEvent::kTouchEnd,
child_frame_monitor.events_received()[4]);
}
IN_PROC_BROWSER_TEST_P(SitePerProcessGestureHitTestBrowserTest,
MainframeGesturePinchGoesToMainFrame) {
SetupRootAndChild();
TestInputEventObserver root_frame_monitor(rwhi_root_);
TestInputEventObserver child_frame_monitor(rwhi_child_);
// Need child rect in main frame coords.
gfx::Rect bounds = rwhv_child_->GetViewBounds();
bounds.Offset(gfx::Point() - rwhva_root_->GetViewBounds().origin());
gfx::Point main_frame_point(bounds.origin());
main_frame_point += gfx::Vector2d(-5, -5);
SendPinchBeginEndSequence(rwhva_root_, main_frame_point, rwhi_root_);
// Verify root-RWHI gets TS/TE/GTD/GSB/GPB/GPE/GSE.
EXPECT_TRUE(root_frame_monitor.EventWasReceived());
EXPECT_EQ(blink::WebInputEvent::kTouchStart,
root_frame_monitor.events_received()[0]);
EXPECT_EQ(blink::WebInputEvent::kGestureTapDown,
root_frame_monitor.events_received()[1]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollBegin,
root_frame_monitor.events_received()[2]);
EXPECT_EQ(blink::WebInputEvent::kGesturePinchBegin,
root_frame_monitor.events_received()[3]);
EXPECT_EQ(blink::WebInputEvent::kGesturePinchEnd,
root_frame_monitor.events_received()[4]);
EXPECT_EQ(blink::WebInputEvent::kGestureScrollEnd,
root_frame_monitor.events_received()[5]);
EXPECT_EQ(blink::WebInputEvent::kTouchEnd,
root_frame_monitor.events_received()[6]);
// Verify child-RWHI gets no events.
EXPECT_FALSE(child_frame_monitor.EventWasReceived());
}
IN_PROC_BROWSER_TEST_P(SitePerProcessGestureHitTestBrowserTest,
SubframeGesturePinchDeniedBySubframeTouchAction) {
SubframeGesturePinchTestHelper("/div_with_touch_action_none.html", false);
}
IN_PROC_BROWSER_TEST_P(SitePerProcessGestureHitTestBrowserTest,
SubframeGesturePinchNoCrash) {
SubframeGesturePinchTestHelper("/div_with_touch_action_auto.html", true);
}
#endif // defined(USE_AURA)
// Test that MouseDown and MouseUp to the same coordinates do not result in
// different coordinates after routing. See bug https://crbug.com/670253.
#if defined(OS_ANDROID)
// Android uses fixed scale factor, which makes this test unnecessary.
#define MAYBE_MouseClickWithNonIntegerScaleFactor \
DISABLED_MouseClickWithNonIntegerScaleFactor
#else
#define MAYBE_MouseClickWithNonIntegerScaleFactor \
MouseClickWithNonIntegerScaleFactor
#endif
IN_PROC_BROWSER_TEST_P(SitePerProcessNonIntegerScaleFactorHitTestBrowserTest,
MAYBE_MouseClickWithNonIntegerScaleFactor) {
GURL initial_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), initial_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
RenderWidgetHostViewBase* rwhv = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostInputEventRouter* router =
static_cast<WebContentsImpl*>(shell()->web_contents())
->GetInputEventRouter();
// Create listener for input events.
RenderWidgetHostMouseEventMonitor event_monitor(
root->current_frame_host()->GetRenderWidgetHost());
blink::WebMouseEvent mouse_event(
blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
mouse_event.button = blink::WebPointerProperties::Button::kLeft;
SetWebEventPositions(&mouse_event, gfx::Point(75, 75), rwhv);
mouse_event.click_count = 1;
event_monitor.ResetEventReceived();
router->RouteMouseEvent(rwhv, &mouse_event, ui::LatencyInfo());
EXPECT_TRUE(event_monitor.EventWasReceived());
gfx::Point mouse_down_coords =
gfx::Point(event_monitor.event().PositionInWidget().x,
event_monitor.event().PositionInWidget().y);
event_monitor.ResetEventReceived();
mouse_event.SetType(blink::WebInputEvent::kMouseUp);
SetWebEventPositions(&mouse_event, gfx::Point(75, 75), rwhv);
router->RouteMouseEvent(rwhv, &mouse_event, ui::LatencyInfo());
EXPECT_TRUE(event_monitor.EventWasReceived());
EXPECT_EQ(mouse_down_coords.x(), event_monitor.event().PositionInWidget().x);
// The transform from browser to renderer is (2, 35) in DIP. When we
// scale that to pixels, it's (3, 53). Note that 35 * 1.5 should be 52.5,
// so we already lost precision there in the transform from draw quad.
EXPECT_NEAR(mouse_down_coords.y(), event_monitor.event().PositionInWidget().y,
kHitTestTolerance);
}
IN_PROC_BROWSER_TEST_P(SitePerProcessNonIntegerScaleFactorHitTestBrowserTest,
MAYBE_NestedSurfaceHitTestTest) {
NestedSurfaceHitTestTestHelper(shell(), embedded_test_server());
}
// Verify RenderWidgetHostInputEventRouter can successfully hit test
// a MouseEvent and route it to a clipped OOPIF.
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, HitTestClippedFrame) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_clipped_iframe.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostInputEventRouter* router =
web_contents()->GetInputEventRouter();
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://127.0.0.1/\n"
" B = http://baz.com/",
DepictFrameTree(root));
FrameTreeNode* child_node = root->child_at(0);
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
RenderWidgetHostMouseEventMonitor root_monitor(
root->current_frame_host()->GetRenderWidgetHost());
RenderWidgetHostMouseEventMonitor child_monitor(
child_node->current_frame_host()->GetRenderWidgetHost());
gfx::PointF point_in_root(25, 25);
gfx::PointF point_in_child(100, 100);
blink::WebMouseEvent down_event(
blink::WebInputEvent::kMouseDown, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
down_event.button = blink::WebPointerProperties::Button::kLeft;
down_event.click_count = 1;
SetWebEventPositions(&down_event, point_in_root, rwhv_root);
blink::WebMouseEvent up_event(
blink::WebInputEvent::kMouseUp, blink::WebInputEvent::kNoModifiers,
blink::WebInputEvent::GetStaticTimeStampForTests());
up_event.button = blink::WebPointerProperties::Button::kLeft;
up_event.click_count = 1;
SetWebEventPositions(&up_event, point_in_root, rwhv_root);
// Target at root.
RouteMouseEventAndWaitUntilDispatch(router, rwhv_root, rwhv_root,
&down_event);
EXPECT_TRUE(root_monitor.EventWasReceived());
EXPECT_FALSE(child_monitor.EventWasReceived());
EXPECT_NEAR(25, root_monitor.event().PositionInWidget().x, kHitTestTolerance);
EXPECT_NEAR(25, root_monitor.event().PositionInWidget().y, kHitTestTolerance);
root_monitor.ResetEventReceived();
child_monitor.ResetEventReceived();
RouteMouseEventAndWaitUntilDispatch(router, rwhv_root, rwhv_root, &up_event);
EXPECT_TRUE(root_monitor.EventWasReceived());
EXPECT_FALSE(child_monitor.EventWasReceived());
EXPECT_NEAR(25, root_monitor.event().PositionInWidget().x, kHitTestTolerance);
EXPECT_NEAR(25, root_monitor.event().PositionInWidget().y, kHitTestTolerance);
// Target at child.
root_monitor.ResetEventReceived();
child_monitor.ResetEventReceived();
SetWebEventPositions(&down_event, point_in_child, rwhv_root);
SetWebEventPositions(&up_event, point_in_child, rwhv_root);
RouteMouseEventAndWaitUntilDispatch(router, rwhv_root, rwhv_child,
&down_event);
// In surface layer hit testing, we should not query client asynchronously.
EXPECT_FALSE(root_monitor.EventWasReceived());
EXPECT_TRUE(child_monitor.EventWasReceived());
EXPECT_NEAR(90, child_monitor.event().PositionInWidget().x,
kHitTestTolerance);
EXPECT_NEAR(100, child_monitor.event().PositionInWidget().y,
kHitTestTolerance);
root_monitor.ResetEventReceived();
child_monitor.ResetEventReceived();
RouteMouseEventAndWaitUntilDispatch(router, rwhv_root, rwhv_child, &up_event);
// We should reuse the target for mouse up.
EXPECT_FALSE(root_monitor.EventWasReceived());
EXPECT_TRUE(child_monitor.EventWasReceived());
EXPECT_TRUE(child_monitor.EventWasReceived());
EXPECT_NEAR(90, child_monitor.event().PositionInWidget().x,
kHitTestTolerance);
EXPECT_NEAR(100, child_monitor.event().PositionInWidget().y,
kHitTestTolerance);
}
// Verify InputTargetClient works within an OOPIF process.
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestBrowserTest, HitTestNestedFrames) {
GURL main_url(embedded_test_server()->GetURL(
"/frame_tree/page_with_positioned_nested_frames.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(1U, root->child_count());
EXPECT_EQ(
" Site A ------------ proxies for B C\n"
" +--Site B ------- proxies for A C\n"
" +--Site C -- proxies for A B\n"
"Where A = http://127.0.0.1/\n"
" B = http://a.com/\n"
" C = http://baz.com/",
DepictFrameTree(root));
FrameTreeNode* child_node = root->child_at(0);
FrameTreeNode* grandchild_node = child_node->child_at(0);
RenderWidgetHostViewBase* rwhv_child = static_cast<RenderWidgetHostViewBase*>(
child_node->current_frame_host()->GetRenderWidgetHost()->GetView());
RenderWidgetHostViewBase* rwhv_grandchild =
static_cast<RenderWidgetHostViewBase*>(
grandchild_node->current_frame_host()
->GetRenderWidgetHost()
->GetView());
WaitForHitTestDataOrChildSurfaceReady(child_node->current_frame_host());
WaitForHitTestDataOrChildSurfaceReady(grandchild_node->current_frame_host());
// Create two points to hit test: One in the child of the main frame, and
// one in the frame nested within that. The hit test request is sent to the
// child's renderer.
gfx::PointF point_in_child(1, 1);
gfx::PointF point_in_nested_child(5, 5);
rwhv_grandchild->TransformPointToCoordSpaceForView(
point_in_nested_child, rwhv_child, &point_in_nested_child);
{
base::RunLoop run_loop;
viz::FrameSinkId received_frame_sink_id;
gfx::PointF returned_point;
base::Closure quit_closure =
content::GetDeferredQuitTaskForRunLoop(&run_loop);
DCHECK_NE(child_node->current_frame_host()->GetInputTargetClient(),
nullptr);
child_node->current_frame_host()->GetInputTargetClient()->FrameSinkIdAt(
point_in_child, 0,
base::BindLambdaForTesting(
[&](const viz::FrameSinkId& id, const gfx::PointF& point) {
received_frame_sink_id = id;
returned_point = point;
quit_closure.Run();
}));
content::RunThisRunLoop(&run_loop);
// |point_in_child| should hit test to the view for |child_node|.
ASSERT_EQ(rwhv_child->GetFrameSinkId(), received_frame_sink_id);
ASSERT_EQ(gfx::PointF(1, 1), returned_point);
}
{
base::RunLoop run_loop;
viz::FrameSinkId received_frame_sink_id;
gfx::PointF returned_point;
base::Closure quit_closure =
content::GetDeferredQuitTaskForRunLoop(&run_loop);
DCHECK_NE(child_node->current_frame_host()->GetInputTargetClient(),
nullptr);
child_node->current_frame_host()->GetInputTargetClient()->FrameSinkIdAt(
point_in_nested_child, 0,
base::BindLambdaForTesting(
[&](const viz::FrameSinkId& id, const gfx::PointF& point) {
received_frame_sink_id = id;
returned_point = point;
quit_closure.Run();
}));
content::RunThisRunLoop(&run_loop);
// |point_in_nested_child| should hit test to |rwhv_grandchild|.
ASSERT_EQ(rwhv_grandchild->GetFrameSinkId(), received_frame_sink_id);
EXPECT_NEAR(returned_point.x(), 5, kHitTestTolerance);
EXPECT_NEAR(returned_point.y(), 5, kHitTestTolerance);
}
}
class SitePerProcessHitTestDataGenerationBrowserTest
: public SitePerProcessHitTestBrowserTest {
public:
SitePerProcessHitTestDataGenerationBrowserTest() {}
protected:
// Load the page |host_name| and retrieve the hit test data from HitTestQuery.
std::vector<viz::AggregatedHitTestRegion> SetupAndGetHitTestData(
const std::string& host_name,
unsigned skipped_child = -1) {
GURL main_url(embedded_test_server()->GetURL(host_name));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
RenderWidgetHostViewBase* rwhv_root =
static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
for (unsigned i = 0; i < root->child_count(); i++) {
// Child with pointer-events: none property will never submit a hit test
// region in /2 hit testing.
if (i != skipped_child) {
WaitForHitTestDataOrChildSurfaceReady(
root->child_at(i)->current_frame_host());
}
}
HitTestRegionObserver observer(rwhv_root->GetRootFrameSinkId());
observer.WaitForHitTestData();
device_scale_factor_ = rwhv_root->GetDeviceScaleFactor();
DCHECK_GT(device_scale_factor_, 0);
auto hit_test_data = observer.GetHitTestData();
MaybeStripHitTestData(&hit_test_data);
return hit_test_data;
}
// Strip the ClientRoot frame sink id from |hit_test_data| when Window
// Service is used because tests are written without considering it and
// using a constant number index to access the data of the interested frame.
// Related to http://crbug.com/895029.
// Note the stripped data has wrong child count and should only be used to
// verify test expectations.
void MaybeStripHitTestData(
std::vector<viz::AggregatedHitTestRegion>* hit_test_data) {
if (!features::IsUsingWindowService())
return;
// There must be at least two frame sink ids: one root and one ClientRoot.
ASSERT_GE(hit_test_data->size(), 2u);
hit_test_data->erase(hit_test_data->begin() + 1);
}
float current_device_scale_factor() const { return device_scale_factor_; }
gfx::QuadF TransformRectToQuadF(const gfx::Rect& rect,
const gfx::Transform& transform,
bool use_scale_factor = true) {
gfx::Rect scaled_rect =
use_scale_factor ? gfx::ScaleToEnclosingRect(rect, device_scale_factor_,
device_scale_factor_)
: rect;
gfx::PointF p1(scaled_rect.origin());
gfx::PointF p2(scaled_rect.top_right());
gfx::PointF p3(scaled_rect.bottom_right());
gfx::PointF p4(scaled_rect.bottom_left());
transform.TransformPoint(&p1);
transform.TransformPoint(&p2);
transform.TransformPoint(&p3);
transform.TransformPoint(&p4);
return gfx::QuadF(p1, p2, p3, p4);
}
gfx::QuadF TransformRectToQuadF(
const viz::AggregatedHitTestRegion& hit_test_region) {
return TransformRectToQuadF(hit_test_region.rect,
hit_test_region.transform(), false);
}
bool ApproximatelyEqual(const gfx::PointF& p1, const gfx::PointF& p2) const {
return std::abs(p1.x() - p2.x()) <= 1 && std::abs(p1.y() - p2.y()) <= 1;
}
bool ApproximatelyEqual(const gfx::QuadF& quad,
const gfx::QuadF& other) const {
return ApproximatelyEqual(quad.p1(), other.p1()) &&
ApproximatelyEqual(quad.p2(), other.p2()) &&
ApproximatelyEqual(quad.p3(), other.p3()) &&
ApproximatelyEqual(quad.p4(), other.p4());
}
gfx::Rect AxisAlignedLayoutRectFromHitTest(
const viz::AggregatedHitTestRegion& hit_test_region) {
DCHECK(hit_test_region.transform().Preserves2dAxisAlignment());
gfx::RectF rect(hit_test_region.rect);
hit_test_region.transform().TransformRect(&rect);
return gfx::ToEnclosingRect(rect);
}
public:
static const uint32_t kFastHitTestFlags;
static const uint32_t kSlowHitTestFlags;
float device_scale_factor_;
};
const uint32_t
SitePerProcessHitTestDataGenerationBrowserTest::kFastHitTestFlags =
viz::HitTestRegionFlags::kHitTestMine |
viz::HitTestRegionFlags::kHitTestChildSurface |
viz::HitTestRegionFlags::kHitTestMouse |
viz::HitTestRegionFlags::kHitTestTouch;
const uint32_t
SitePerProcessHitTestDataGenerationBrowserTest::kSlowHitTestFlags =
SitePerProcessHitTestDataGenerationBrowserTest::kFastHitTestFlags |
viz::HitTestRegionFlags::kHitTestAsk;
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestDataGenerationBrowserTest,
TransformedOOPIF) {
if (!features::IsVizHitTestingEnabled())
return;
auto hit_test_data =
SetupAndGetHitTestData("/frame_tree/page_with_transformed_iframe.html");
float device_scale_factor = current_device_scale_factor();
// Compute screen space transform for iframe element.
gfx::Transform expected_transform;
gfx::Transform translate;
expected_transform.RotateAboutZAxis(-45);
translate.Translate(-100 * device_scale_factor, -100 * device_scale_factor);
expected_transform.PreconcatTransform(translate);
DCHECK(hit_test_data.size() >= 3);
// The iframe element in main page is transformed and also clips the content
// of the subframe, so we expect to do slow path hit testing in this case.
// TODO(sunxd): We should do fast path hit testing in this case. See
// https://crbug.com/851507.
EXPECT_TRUE(ApproximatelyEqual(
TransformRectToQuadF(gfx::Rect(100, 100), expected_transform),
TransformRectToQuadF(hit_test_data[2])));
EXPECT_EQ(kSlowHitTestFlags, hit_test_data[2].flags);
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestDataGenerationBrowserTest,
ClippedOOPIFFastPath) {
if (!features::IsVizHitTestingEnabled())
return;
auto hit_test_data =
SetupAndGetHitTestData("/frame_tree/page_with_clipped_iframe.html");
float device_scale_factor = current_device_scale_factor();
gfx::Transform expected_transform;
// In V1 hit testing or V2 hit testing slow path, we expected unclipped iframe
// bounds in its own space.
gfx::Rect original_region(200, 200);
gfx::Rect expected_transformed_region = gfx::ScaleToEnclosingRect(
original_region, device_scale_factor, device_scale_factor);
uint32_t expected_flags = kFastHitTestFlags;
// Clip2 has overflow: visible property, so it does not apply clip to iframe.
// Clip1 and clip3 all preserve 2d axis alignment, so we should allow fast
// path hit testing for the iframe in V2 hit testing.
// When VizDisplayCompositor is enabled, HitTestDataProviderDrawQuad will
// override LTHI's hit test data.
if (features::IsVizHitTestingDrawQuadEnabled()) {
// In V1 hit testing, we expect slow path and the submitted region should be
// equivalent to the unclipped iframe bounds.
expected_flags = kSlowHitTestFlags;
} else if (features::IsVizHitTestingSurfaceLayerEnabled()) {
// In V2 hit testing fast path, we expect precise clipped iframe bounds in
// its own space.
expected_transformed_region = gfx::ScaleToEnclosingRect(
gfx::Rect(100, 100), device_scale_factor, device_scale_factor);
}
// Apart from the iframe, it also contains data for root and main frame.
DCHECK(hit_test_data.size() >= 3);
EXPECT_TRUE(expected_transformed_region.ApproximatelyEqual(
AxisAlignedLayoutRectFromHitTest(hit_test_data[2]),
gfx::ToRoundedInt(device_scale_factor) + 2));
EXPECT_TRUE(
expected_transform.ApproximatelyEqual(hit_test_data[2].transform()));
EXPECT_EQ(expected_flags, hit_test_data[2].flags);
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestDataGenerationBrowserTest,
RotatedClippedOOPIF) {
if (!features::IsVizHitTestingEnabled())
return;
auto hit_test_data = SetupAndGetHitTestData(
"/frame_tree/page_with_rotated_clipped_iframe.html");
float device_scale_factor = current_device_scale_factor();
// +-Root
// +---clip1
// +-----clip2 rotateZ(45)
// +-------clip3 rotateZ(-45)
// +---------iframe
//
// +----------------300px--------------+
// |\ |
// | \ |
// | \ 100px
// |- x --\ |
// | / |
// +-----------------------------------+
//
// Clipped region: x=100/sqrt(2), y=100.
gfx::Transform expected_transform;
gfx::Rect expected_region = gfx::ScaleToEnclosingRect(
gfx::Rect(200, 200), device_scale_factor, device_scale_factor);
if (!features::IsVizHitTestingDrawQuadEnabled()) {
expected_region = gfx::ScaleToEnclosingRect(
gfx::Rect(100 / 1.414, 100), device_scale_factor, device_scale_factor);
}
// Compute screen space transform for iframe element, since clip2 is rotated
// and also clips the iframe, we expect to do slow path hit test on the
// iframe.
DCHECK(hit_test_data.size() >= 3);
EXPECT_TRUE(expected_region.ApproximatelyEqual(hit_test_data[2].rect,
1 + device_scale_factor));
EXPECT_TRUE(
expected_transform.ApproximatelyEqual(hit_test_data[2].transform()));
EXPECT_EQ(kSlowHitTestFlags, hit_test_data[2].flags);
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestDataGenerationBrowserTest,
ClippedRotatedOOPIF) {
if (!features::IsVizHitTestingEnabled())
return;
auto hit_test_data = SetupAndGetHitTestData(
"/frame_tree/page_with_clipped_rotated_iframe.html");
float device_scale_factor = current_device_scale_factor();
// +-Root
// +---clip1
// +---------iframe rotateZ(45deg)
//
// There are actually 2 clips applied to surface layer, in root space they
// are:
// bounding box of clip1: rect 0, 0 300x100, transform = identity;
// bounding box of iframe itself: rect -100*sqrt(2), 0 200*sqrt(2)x200*sqrt(2)
// transform: rotateZ(45).
// In root space the two clips accumulates to:
// rect 0, 0 100*sqrt(2)x100, transform=identity
// Transform this to layer's local space, the clip rect is:
// rect 0, -100/sqrt(2) (100+100/sqrt(2))x(100/sqrt(2))
// So the intersected visible layer rect is:
// rect 0, 0, (100+100/sqrt(2)), 100/sqrt(2).
// +----------------300px--------------+
// |\ |
// | \ |
// | \x 100px
// | / \ |
// | /y \ |
// +-----------------------------------+
gfx::Transform expected_transform;
expected_transform.RotateAboutZAxis(-45);
gfx::Rect expected_region1 = gfx::ScaleToEnclosingRect(
gfx::Rect(200, 200), device_scale_factor, device_scale_factor);
gfx::Rect expected_region2;
if (features::IsVizHitTestingSurfaceLayerEnabled()) {
// The clip tree built by BlinkGenPropertyTrees is different from that build
// by cc. While it does not affect correctness of hit testing, the hit test
// region with kHitTestAsk will have a different size due to the change of
// accumulated clips.
expected_region1 = gfx::ScaleToEnclosingRect(
gfx::Rect(200, 100 / 1.414f), device_scale_factor, device_scale_factor);
expected_region2 =
gfx::ScaleToEnclosingRect(gfx::Rect(100 + 100 / 1.414f, 100 / 1.414f),
device_scale_factor, device_scale_factor);
}
// Since iframe is clipped into an octagon, we expect to do slow path hit
// test on the iframe.
DCHECK(hit_test_data.size() >= 3);
if (!features::IsVizHitTestingSurfaceLayerEnabled()) {
EXPECT_TRUE(expected_region1.ApproximatelyEqual(hit_test_data[2].rect,
1 + device_scale_factor));
} else {
EXPECT_TRUE(expected_region1.ApproximatelyEqual(hit_test_data[2].rect,
1 + device_scale_factor) ||
expected_region2.ApproximatelyEqual(hit_test_data[2].rect,
1 + device_scale_factor));
}
EXPECT_TRUE(
expected_transform.ApproximatelyEqual(hit_test_data[2].transform()));
EXPECT_EQ(kSlowHitTestFlags, hit_test_data[2].flags);
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestDataGenerationBrowserTest,
ClipPathOOPIF) {
if (!features::IsVizHitTestingEnabled())
return;
auto hit_test_data =
SetupAndGetHitTestData("/frame_tree/page_with_clip_path_iframe.html");
float device_scale_factor = current_device_scale_factor();
gfx::Transform expected_transform;
gfx::Rect expected_region1 = gfx::ScaleToEnclosingRect(
gfx::Rect(100, 100), device_scale_factor, device_scale_factor);
gfx::Rect expected_region2 = gfx::ScaleToEnclosingRect(
gfx::Rect(80, 80), device_scale_factor, device_scale_factor);
// Since iframe is clipped into an irregular quadrilateral, we expect to do
// slow path hit test on the iframe.
DCHECK(hit_test_data.size() >= 3);
// When BlinkGenPropertyTrees is enabled, the visible rect calculated for the
// OOPIF is different to that when BlinkGenPropertyTrees is disabled. So the
// test is considered passed if either of the regions equals to hit test
// region.
if (features::IsVizHitTestingSurfaceLayerEnabled()) {
EXPECT_TRUE(expected_region1.ApproximatelyEqual(hit_test_data[2].rect,
1 + device_scale_factor) ||
expected_region2.ApproximatelyEqual(hit_test_data[2].rect,
1 + device_scale_factor));
} else {
EXPECT_TRUE(expected_region1.ApproximatelyEqual(hit_test_data[2].rect,
1 + device_scale_factor));
}
EXPECT_TRUE(
expected_transform.ApproximatelyEqual(hit_test_data[2].transform()));
EXPECT_EQ(kSlowHitTestFlags, hit_test_data[2].flags);
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestDataGenerationBrowserTest,
OverlappedOOPIF) {
if (!features::IsVizHitTestingEnabled())
return;
auto hit_test_data =
SetupAndGetHitTestData("/frame_tree/page_with_overlapped_iframes.html");
float device_scale_factor = current_device_scale_factor();
gfx::Transform expected_transform1;
gfx::Transform expected_transform2;
expected_transform2.matrix().postTranslate(-100 * device_scale_factor, 0, 0);
gfx::Rect expected_region = gfx::ScaleToEnclosingRect(
gfx::Rect(100, 100), device_scale_factor, device_scale_factor);
// Since iframe is occluded by a div in parent frame, we expect to do slow hit
// test.
DCHECK(hit_test_data.size() >= 4);
EXPECT_EQ(expected_region.ToString(), hit_test_data[3].rect.ToString());
EXPECT_TRUE(
expected_transform1.ApproximatelyEqual(hit_test_data[3].transform()));
EXPECT_EQ(kSlowHitTestFlags, hit_test_data[3].flags);
EXPECT_EQ(expected_region.ToString(), hit_test_data[2].rect.ToString());
EXPECT_TRUE(
expected_transform2.ApproximatelyEqual(hit_test_data[2].transform()));
if (features::IsVizHitTestingDrawQuadEnabled())
EXPECT_EQ(kSlowHitTestFlags, hit_test_data[2].flags);
else if (features::IsVizHitTestingSurfaceLayerEnabled())
EXPECT_EQ(kFastHitTestFlags, hit_test_data[2].flags);
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestDataGenerationBrowserTest,
MaskedOOPIF) {
if (!features::IsVizHitTestingEnabled())
return;
auto hit_test_data =
SetupAndGetHitTestData("/frame_tree/page_with_masked_iframe.html");
float device_scale_factor = current_device_scale_factor();
gfx::Transform expected_transform;
gfx::Rect expected_region = gfx::ScaleToEnclosingRect(
gfx::Rect(200, 200), device_scale_factor, device_scale_factor);
// Since iframe clipped by clip-path and has a mask layer, we expect to do
// slow path hit testing.
DCHECK(hit_test_data.size() >= 3);
EXPECT_EQ(expected_region.ToString(), hit_test_data[2].rect.ToString());
EXPECT_TRUE(
expected_transform.ApproximatelyEqual(hit_test_data[2].transform()));
EXPECT_EQ(kSlowHitTestFlags, hit_test_data[2].flags);
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestDataGenerationBrowserTest,
AncestorMaskedOOPIF) {
if (!features::IsVizHitTestingEnabled())
return;
auto hit_test_data = SetupAndGetHitTestData(
"/frame_tree/page_with_ancestor_masked_iframe.html");
float device_scale_factor = current_device_scale_factor();
gfx::Transform expected_transform;
gfx::Rect expected_region;
if (features::IsVizHitTestingSurfaceLayerEnabled()) {
expected_region = gfx::ScaleToEnclosingRect(
gfx::Rect(100, 100), device_scale_factor, device_scale_factor);
} else {
expected_region = gfx::ScaleToEnclosingRect(
gfx::Rect(200, 200), device_scale_factor, device_scale_factor);
}
// Since iframe clipped by clip-path and has a mask layer, we expect to do
// slow path hit testing.
DCHECK(hit_test_data.size() >= 3);
EXPECT_EQ(expected_region.ToString(), hit_test_data[2].rect.ToString());
EXPECT_TRUE(
expected_transform.ApproximatelyEqual(hit_test_data[2].transform()));
EXPECT_EQ(kSlowHitTestFlags, hit_test_data[2].flags);
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestDataGenerationBrowserTest,
PointerEventsNoneOOPIF) {
if (!features::IsVizHitTestingEnabled())
return;
auto hit_test_data = SetupAndGetHitTestData(
"/frame_tree/page_with_positioned_frame_pointer-events_none.html", 0);
float device_scale_factor = current_device_scale_factor();
gfx::Transform expected_transform;
gfx::Rect expected_region = gfx::ScaleToEnclosingRect(
gfx::Rect(1, 1), device_scale_factor, device_scale_factor);
expected_transform.Translate(-2 * device_scale_factor,
-2 * device_scale_factor);
gfx::Rect expected_region2 = gfx::ScaleToEnclosingRect(
gfx::Rect(100, 100), device_scale_factor, device_scale_factor);
gfx::Transform expected_transform2;
expected_transform2.Translate(-52 * device_scale_factor,
-52 * device_scale_factor);
// We submit hit test region for OOPIFs with pointer-events: none, and mark
// them as kHitTestIgnore.
uint32_t flags = features::IsVizHitTestingSurfaceLayerEnabled()
? kFastHitTestFlags
: kSlowHitTestFlags;
DCHECK(hit_test_data.size() == 4);
EXPECT_EQ(expected_region2.ToString(), hit_test_data[3].rect.ToString());
EXPECT_TRUE(
expected_transform2.ApproximatelyEqual(hit_test_data[3].transform()));
EXPECT_EQ(flags | viz::HitTestRegionFlags::kHitTestIgnore,
hit_test_data[3].flags);
EXPECT_EQ(expected_region.ToString(), hit_test_data[2].rect.ToString());
EXPECT_TRUE(
expected_transform.ApproximatelyEqual(hit_test_data[2].transform()));
EXPECT_EQ(flags, hit_test_data[2].flags);
// Check that an update on the css property can trigger an update in submitted
// hit test data.
EXPECT_TRUE(ExecuteScript(web_contents(),
"document.getElementsByTagName('iframe')[0].style."
"pointerEvents = 'auto';\n"));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
ASSERT_EQ(2U, root->child_count());
RenderWidgetHostViewBase* rwhv_root = static_cast<RenderWidgetHostViewBase*>(
root->current_frame_host()->GetRenderWidgetHost()->GetView());
{
MainThreadFrameObserver observer(
root->current_frame_host()->GetRenderWidgetHost());
observer.Wait();
}
{
MainThreadFrameObserver observer(
root->child_at(0)->current_frame_host()->GetRenderWidgetHost());
observer.Wait();
}
{
MainThreadFrameObserver observer(
root->child_at(1)->current_frame_host()->GetRenderWidgetHost());
observer.Wait();
}
WaitForHitTestDataOrChildSurfaceReady(
root->child_at(0)->current_frame_host());
WaitForHitTestDataOrChildSurfaceReady(
root->child_at(1)->current_frame_host());
HitTestRegionObserver observer(rwhv_root->GetRootFrameSinkId());
observer.WaitForHitTestData();
hit_test_data = observer.GetHitTestData();
MaybeStripHitTestData(&hit_test_data);
DCHECK(hit_test_data.size() == 4);
EXPECT_EQ(expected_region.ToString(), hit_test_data[2].rect.ToString());
EXPECT_TRUE(
expected_transform.ApproximatelyEqual(hit_test_data[2].transform()));
// Non v2 hit-testing should still treat OOPIFs as slow path.
if (features::IsVizHitTestingSurfaceLayerEnabled()) {
EXPECT_EQ(kFastHitTestFlags, hit_test_data[2].flags);
} else {
EXPECT_EQ(kSlowHitTestFlags, hit_test_data[2].flags);
}
EXPECT_EQ(expected_region2.ToString(), hit_test_data[3].rect.ToString());
EXPECT_TRUE(
expected_transform2.ApproximatelyEqual(hit_test_data[3].transform()));
if (features::IsVizHitTestingSurfaceLayerEnabled()) {
EXPECT_EQ(kFastHitTestFlags, hit_test_data[3].flags);
} else {
EXPECT_EQ(kSlowHitTestFlags, hit_test_data[3].flags);
}
}
IN_PROC_BROWSER_TEST_P(SitePerProcessHitTestDataGenerationBrowserTest,
OccludedOOPIF) {
if (!features::IsVizHitTestingSurfaceLayerEnabled())
return;
auto hit_test_data =
SetupAndGetHitTestData("/frame_tree/page_with_occluded_iframes.html");
float device_scale_factor = current_device_scale_factor();
gfx::Transform expected_transform1;
gfx::Transform expected_transform2;
expected_transform2.Translate(-110 * device_scale_factor, 0);
// We should not skip OOPIFs that are occluded by parent frame elements, since
// in cc an element's bound may not be its hit test area.
DCHECK(hit_test_data.size() == 4);
EXPECT_TRUE(ApproximatelyEqual(
TransformRectToQuadF(gfx::Rect(100, 100), expected_transform1),
TransformRectToQuadF(hit_test_data[3])));
EXPECT_EQ(kSlowHitTestFlags, hit_test_data[3].flags);
EXPECT_TRUE(ApproximatelyEqual(
TransformRectToQuadF(gfx::Rect(100, 100), expected_transform2),
TransformRectToQuadF(hit_test_data[2])));
EXPECT_EQ(kSlowHitTestFlags, hit_test_data[2].flags);
}
static const int kHitTestOption[] = {0, 1, 2};
static const float kOneScale[] = {1.f};
INSTANTIATE_TEST_SUITE_P(/* no prefix */,
SitePerProcessHitTestBrowserTest,
testing::Combine(testing::ValuesIn(kHitTestOption),
testing::ValuesIn(kOneScale)));
// TODO(wjmaclean): Since the next two test fixtures only differ in DSF
// values, should we combine them into one using kMultiScale? This
// approach would make it more difficult to disable individual scales on
// particular platforms.
INSTANTIATE_TEST_SUITE_P(/* no prefix */,
SitePerProcessHighDPIHitTestBrowserTest,
testing::Combine(testing::ValuesIn(kHitTestOption),
testing::ValuesIn(kOneScale)));
INSTANTIATE_TEST_SUITE_P(/* no prefix */,
SitePerProcessNonIntegerScaleFactorHitTestBrowserTest,
testing::Combine(testing::ValuesIn(kHitTestOption),
testing::ValuesIn(kOneScale)));
INSTANTIATE_TEST_SUITE_P(/* no prefix */,
SitePerProcessEmulatedTouchBrowserTest,
testing::Combine(testing::ValuesIn(kHitTestOption),
testing::ValuesIn(kOneScale)));
INSTANTIATE_TEST_SUITE_P(/* no prefix */,
SitePerProcessHitTestDataGenerationBrowserTest,
testing::Combine(testing::ValuesIn(kHitTestOption),
testing::ValuesIn(kOneScale)));
#if defined(USE_AURA)
static const float kMultiScale[] = {1.f, 1.5f, 2.f};
INSTANTIATE_TEST_SUITE_P(/* no prefix */,
SitePerProcessInternalsHitTestBrowserTest,
testing::Combine(testing::ValuesIn(kHitTestOption),
testing::ValuesIn(kMultiScale)));
INSTANTIATE_TEST_SUITE_P(/* no prefix */,
SitePerProcessMouseWheelHitTestBrowserTest,
testing::Combine(testing::ValuesIn(kHitTestOption),
testing::ValuesIn(kOneScale)));
INSTANTIATE_TEST_SUITE_P(/* no prefix */,
SitePerProcessGestureHitTestBrowserTest,
testing::Combine(testing::ValuesIn(kHitTestOption),
testing::ValuesIn(kOneScale)));
#endif
} // namespace content