Commit 89ed0412 authored by kenrb's avatar kenrb Committed by Commit bot

Generate MouseEnter/MouseLeave events between processes.

As the mouse cursor moves across OOPIF boundaries, elements in
other processes that have certain mouse event handlers might require
notification in order to fire those handlers. An example is MouseLeave,
which is not currently fired when the MouseMove is directed to a
parent frame.

This CL tracks the last RenderWidgetHostView to receive a mouse
event and sends appropriate events to other renderers when that changes.

BUG=632035

Review-Url: https://codereview.chromium.org/2229463004
Cr-Commit-Position: refs/heads/master@{#418629}
parent f9779cbd
......@@ -40,4 +40,9 @@ specific_include_rules = {
"render_widget_host_view_aura\.cc": [
"+content/browser/frame_host",
],
# TODO(kenrb): RenderWidgetHostViewChildFrame should eventually be moved
# to content/renderer_host, at which time this can be removed.
"render_widget_host_input_event_router.cc": [
"+content/browser/frame_host/render_widget_host_view_child_frame.h",
],
}
......@@ -4,10 +4,14 @@
#include "content/browser/renderer_host/render_widget_host_input_event_router.h"
#include <vector>
#include "base/metrics/histogram_macros.h"
#include "cc/quads/surface_draw_quad.h"
#include "cc/surfaces/surface_id_allocator.h"
#include "cc/surfaces/surface_manager.h"
#include "content/browser/frame_host/render_widget_host_view_child_frame.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_base.h"
#include "content/common/frame_messages.h"
......@@ -70,6 +74,21 @@ void RenderWidgetHostInputEventRouter::OnRenderWidgetHostViewBaseDestroyed(
bubbling_gesture_scroll_target_.target = nullptr;
first_bubbling_scroll_target_.target = nullptr;
}
if (view == last_mouse_move_target_) {
// When a child iframe is destroyed, consider its parent to be to be the
// most recent target, if possible. In some cases the parent might already
// have been destroyed, in which case the last target is cleared.
if (view != last_mouse_move_root_view_)
last_mouse_move_target_ =
static_cast<RenderWidgetHostViewChildFrame*>(last_mouse_move_target_)
->GetParentView();
else
last_mouse_move_target_ = nullptr;
if (!last_mouse_move_target_)
last_mouse_move_root_view_ = nullptr;
}
}
void RenderWidgetHostInputEventRouter::ClearAllObserverRegistrations() {
......@@ -102,7 +121,9 @@ bool RenderWidgetHostInputEventRouter::HittestDelegate::AcceptHitTarget(
}
RenderWidgetHostInputEventRouter::RenderWidgetHostInputEventRouter()
: active_touches_(0),
: last_mouse_move_target_(nullptr),
last_mouse_move_root_view_(nullptr),
active_touches_(0),
in_touchscreen_gesture_pinch_(false),
gesture_pinch_did_send_scroll_begin_(false) {}
......@@ -153,6 +174,14 @@ void RenderWidgetHostInputEventRouter::RouteMouseEvent(
if (!target)
return;
// SendMouseEnterOrLeaveEvents is called with the original event
// coordinates, which are transformed independently for each view that will
// receive an event.
if ((event->type == blink::WebInputEvent::MouseLeave ||
event->type == blink::WebInputEvent::MouseMove) &&
target != last_mouse_move_target_)
SendMouseEnterOrLeaveEvents(event, target, root_view);
event->x = transformed_point.x();
event->y = transformed_point.y();
// TODO(wjmaclean): Initialize latency info correctly for OOPIFs.
......@@ -292,6 +321,113 @@ void RenderWidgetHostInputEventRouter::RouteTouchEvent(
}
}
void RenderWidgetHostInputEventRouter::SendMouseEnterOrLeaveEvents(
blink::WebMouseEvent* event,
RenderWidgetHostViewBase* target,
RenderWidgetHostViewBase* root_view) {
// This method treats RenderWidgetHostViews as a tree, where the mouse
// cursor is potentially leaving one node and entering another somewhere
// else in the tree. Since iframes are graphically self-contained (i.e. an
// iframe can't have a descendant that renders outside of its rect
// boundaries), all affected RenderWidgetHostViews are ancestors of either
// the node being exited or the node being entered.
// Approach:
// 1. Find lowest common ancestor (LCA) of the last view and current target
// view.
// 2. The last view, and its ancestors up to but not including the LCA,
// receive a MouseLeave.
// 3. The LCA itself, unless it is the new target, receives a MouseOut
// because the cursor has passed between elements within its bounds.
// 4. The new target view's ancestors, up to but not including the LCA,
// receive a MouseEnter.
// Ordering does not matter since these are handled asynchronously relative
// to each other.
// If the mouse has moved onto a different root view (typically meaning it
// has crossed over a popup or context menu boundary), then we invalidate
// last_mouse_move_target_ because we have no reference for its coordinate
// space.
if (root_view != last_mouse_move_root_view_)
last_mouse_move_target_ = nullptr;
// Finding the LCA uses a standard approach. We build vectors of the
// ancestors of each node up to the root, and then remove common ancestors.
std::vector<RenderWidgetHostViewBase*> entered_views;
std::vector<RenderWidgetHostViewBase*> exited_views;
RenderWidgetHostViewBase* cur_view = target;
entered_views.push_back(cur_view);
while (cur_view != root_view) {
// Non-root RWHVs are guaranteed to be RenderWidgetHostViewChildFrames.
cur_view =
static_cast<RenderWidgetHostViewChildFrame*>(cur_view)->GetParentView();
// cur_view can possibly be nullptr for guestviews that are not currently
// connected to the webcontents tree.
if (!cur_view)
break;
entered_views.push_back(cur_view);
}
cur_view = last_mouse_move_target_;
if (cur_view) {
exited_views.push_back(cur_view);
while (cur_view != root_view) {
cur_view = static_cast<RenderWidgetHostViewChildFrame*>(cur_view)
->GetParentView();
if (!cur_view)
break;
exited_views.push_back(cur_view);
}
}
// This removes common ancestors from the root downward.
RenderWidgetHostViewBase* common_ancestor = nullptr;
while (entered_views.size() > 0 && exited_views.size() > 0 &&
entered_views.back() == exited_views.back()) {
common_ancestor = entered_views.back();
entered_views.pop_back();
exited_views.pop_back();
}
gfx::Point transformed_point;
// Send MouseLeaves.
for (auto view : exited_views) {
blink::WebMouseEvent mouse_leave(*event);
mouse_leave.type = blink::WebInputEvent::MouseLeave;
transformed_point = root_view->TransformPointToCoordSpaceForView(
gfx::Point(event->x, event->y), view);
mouse_leave.x = transformed_point.x();
mouse_leave.y = transformed_point.y();
view->ProcessMouseEvent(mouse_leave, ui::LatencyInfo());
}
// The ancestor might need to trigger MouseOut handlers.
if (common_ancestor && common_ancestor != target) {
blink::WebMouseEvent mouse_move(*event);
mouse_move.type = blink::WebInputEvent::MouseMove;
transformed_point = root_view->TransformPointToCoordSpaceForView(
gfx::Point(event->x, event->y), common_ancestor);
mouse_move.x = transformed_point.x();
mouse_move.y = transformed_point.y();
common_ancestor->ProcessMouseEvent(mouse_move, ui::LatencyInfo());
}
// Send MouseMoves to trigger MouseEnter handlers.
for (auto view : entered_views) {
if (view == target)
continue;
blink::WebMouseEvent mouse_enter(*event);
mouse_enter.type = blink::WebInputEvent::MouseMove;
transformed_point = root_view->TransformPointToCoordSpaceForView(
gfx::Point(event->x, event->y), view);
mouse_enter.x = transformed_point.x();
mouse_enter.y = transformed_point.y();
view->ProcessMouseEvent(mouse_enter, ui::LatencyInfo());
}
last_mouse_move_target_ = target;
last_mouse_move_root_view_ = root_view;
}
void RenderWidgetHostInputEventRouter::BubbleScrollEvent(
RenderWidgetHostViewBase* target_view,
const blink::WebGestureEvent& event) {
......
......@@ -121,6 +121,15 @@ class CONTENT_EXPORT RenderWidgetHostInputEventRouter
blink::WebGestureEvent* event,
const ui::LatencyInfo& latency);
// MouseMove/Enter/Leave events might need to be processed by multiple frames
// in different processes for MouseEnter and MouseLeave event handlers to
// properly fire. This method determines which RenderWidgetHostViews other
// than the actual target require notification, and sends the appropriate
// events to them.
void SendMouseEnterOrLeaveEvents(blink::WebMouseEvent* event,
RenderWidgetHostViewBase* target,
RenderWidgetHostViewBase* root_view);
// The following methods take a GestureScrollUpdate event and send a
// GestureScrollBegin or GestureScrollEnd for wrapping it. This is needed
// when GestureScrollUpdates are being forwarded for scroll bubbling.
......@@ -136,6 +145,11 @@ class CONTENT_EXPORT RenderWidgetHostInputEventRouter
TargetData touchpad_gesture_target_;
TargetData bubbling_gesture_scroll_target_;
TargetData first_bubbling_scroll_target_;
// Tracked for the purpose of generating MouseEnter and MouseLeave events.
RenderWidgetHostViewBase* last_mouse_move_target_;
RenderWidgetHostViewBase* last_mouse_move_root_view_;
int active_touches_;
// Keep track of when we are between GesturePinchBegin and GesturePinchEnd
// inclusive, as we need to route these events (and anything in between) to
......
......@@ -1298,6 +1298,119 @@ IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
EXPECT_FALSE(child_frame_monitor.EventWasReceived());
}
// This test verifies that MouseEnter and MouseLeave events fire correctly
// when the mouse cursor moves between processes.
#if defined(OS_ANDROID)
// Browser process hit testing is not implemented on Android.
// https://crbug.com/491334
#define MAYBE_CrossProcessMouseEnterAndLeaveTest \
DISABLED_CrossProcessMouseEnterAndLeaveTest
#else
#define MAYBE_CrossProcessMouseEnterAndLeaveTest \
CrossProcessMouseEnterAndLeaveTest
#endif
IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
MAYBE_CrossProcessMouseEnterAndLeaveTest) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b,c(d))"));
NavigateToURL(shell(), main_url);
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.
SurfaceHitTestReadyNotifier notifier_b(
static_cast<RenderWidgetHostViewChildFrame*>(rwhv_b));
notifier_b.WaitForSurfaceReady();
SurfaceHitTestReadyNotifier notifier_d(
static_cast<RenderWidgetHostViewChildFrame*>(rwhv_d));
notifier_d.WaitForSurfaceReady();
// 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());
gfx::Point point_in_a_frame(2, 2);
gfx::Point point_in_b_frame(rwhv_b->GetViewBounds().x() + 2,
rwhv_b->GetViewBounds().y() + 2);
gfx::Point point_in_d_frame(rwhv_d->GetViewBounds().x() + 2,
rwhv_d->GetViewBounds().y() + 2);
blink::WebMouseEvent mouse_event;
mouse_event.type = blink::WebInputEvent::MouseMove;
mouse_event.x = point_in_a_frame.x();
mouse_event.y = point_in_a_frame.y();
// Send an initial MouseMove to the root view, which shouldn't affect the
// other renderers.
web_contents()->GetInputEventRouter()->RouteMouseEvent(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.
mouse_event.x = point_in_b_frame.x();
mouse_event.y = point_in_b_frame.y();
web_contents()->GetInputEventRouter()->RouteMouseEvent(rwhv_a, &mouse_event);
EXPECT_TRUE(a_frame_monitor.EventWasReceived());
EXPECT_EQ(a_frame_monitor.event().type, blink::WebInputEvent::MouseMove);
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.
mouse_event.x = point_in_d_frame.x();
mouse_event.y = point_in_d_frame.y();
web_contents()->GetInputEventRouter()->RouteMouseEvent(rwhv_a, &mouse_event);
EXPECT_TRUE(a_frame_monitor.EventWasReceived());
EXPECT_EQ(a_frame_monitor.event().type, blink::WebInputEvent::MouseMove);
EXPECT_TRUE(b_frame_monitor.EventWasReceived());
EXPECT_EQ(b_frame_monitor.event().type, blink::WebInputEvent::MouseLeave);
EXPECT_TRUE(c_frame_monitor.EventWasReceived());
EXPECT_EQ(c_frame_monitor.event().type, blink::WebInputEvent::MouseMove);
EXPECT_TRUE(d_frame_monitor.EventWasReceived());
}
// Tests OOPIF rendering by checking that the RWH of the iframe generates
// OnSwapCompositorFrame message.
#if defined(OS_ANDROID)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment