Index: content/browser/renderer_host/render_widget_host_input_event_router.cc |
diff --git a/content/browser/renderer_host/render_widget_host_input_event_router.cc b/content/browser/renderer_host/render_widget_host_input_event_router.cc |
index 3710a90df7f9d578833047c019537275a9ee9b4b..10b868cc860d828170978ed7e07cfaebed19c208 100644 |
--- a/content/browser/renderer_host/render_widget_host_input_event_router.cc |
+++ b/content/browser/renderer_host/render_widget_host_input_event_router.cc |
@@ -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" |
@@ -64,6 +68,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_) |
Navid Zolghadr
2016/09/14 14:51:46
nit: I guess the ideal solution is that target sho
|
+ ->GetParentView(); |
+ else |
+ last_mouse_move_target_ = nullptr; |
+ |
+ if (!last_mouse_move_target_) |
+ last_mouse_move_root_view_ = nullptr; |
+ } |
} |
void RenderWidgetHostInputEventRouter::ClearAllObserverRegistrations() { |
@@ -96,7 +115,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) {} |
@@ -147,6 +168,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. |
@@ -286,6 +315,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) { |