blob: d537fb13527089194d5f1928f97745dd90870966 [file] [log] [blame]
// Copyright 2015 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/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_manager.h"
#include "content/browser/frame_host/render_widget_host_view_child_frame.h"
#include "content/browser/frame_host/render_widget_host_view_guest.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"
#include "third_party/WebKit/public/platform/WebInputEvent.h"
#include "ui/events/blink/web_input_event_traits.h"
namespace {
void TransformEventTouchPositions(blink::WebTouchEvent* event,
const gfx::Vector2d& delta) {
for (unsigned i = 0; i < event->touches_length; ++i) {
event->touches[i].SetPositionInWidget(
event->touches[i].PositionInWidget().x + delta.x(),
event->touches[i].PositionInWidget().y + delta.y());
}
}
blink::WebGestureEvent DummyGestureScrollUpdate(double timeStampSeconds) {
return blink::WebGestureEvent(blink::WebInputEvent::kGestureScrollUpdate,
blink::WebInputEvent::kNoModifiers,
timeStampSeconds);
}
} // anonymous namespace
namespace content {
void RenderWidgetHostInputEventRouter::OnRenderWidgetHostViewBaseDestroyed(
RenderWidgetHostViewBase* view) {
view->RemoveObserver(this);
// Remove this view from the owner_map.
for (auto entry : owner_map_) {
if (entry.second == view) {
owner_map_.erase(entry.first);
// There will only be one instance of a particular view in the map.
break;
}
}
if (view == touch_target_.target) {
touch_target_.target = nullptr;
active_touches_ = 0;
}
// If the target that's being destroyed is in the gesture target queue, we
// replace it with nullptr so that we maintain the 1:1 correspondence between
// queue entries and the touch sequences that underly them.
for (size_t i = 0; i < touchscreen_gesture_target_queue_.size(); ++i) {
if (touchscreen_gesture_target_queue_[i].target == view)
touchscreen_gesture_target_queue_[i].target = nullptr;
}
if (view == mouse_capture_target_.target)
mouse_capture_target_.target = nullptr;
if (view == touchscreen_gesture_target_.target)
touchscreen_gesture_target_.target = nullptr;
if (view == touchpad_gesture_target_.target)
touchpad_gesture_target_.target = nullptr;
if (view == bubbling_gesture_scroll_target_.target ||
view == first_bubbling_scroll_target_.target) {
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_ || view == last_mouse_move_root_view_)
last_mouse_move_root_view_ = nullptr;
}
}
void RenderWidgetHostInputEventRouter::ClearAllObserverRegistrations() {
for (auto entry : owner_map_)
entry.second->RemoveObserver(this);
owner_map_.clear();
}
RenderWidgetHostInputEventRouter::HittestDelegate::HittestDelegate(
const std::unordered_map<cc::SurfaceId, HittestData, cc::SurfaceIdHash>&
hittest_data)
: hittest_data_(hittest_data) {}
bool RenderWidgetHostInputEventRouter::HittestDelegate::RejectHitTarget(
const cc::SurfaceDrawQuad* surface_quad,
const gfx::Point& point_in_quad_space) {
auto it = hittest_data_.find(surface_quad->surface_id);
if (it != hittest_data_.end() && it->second.ignored_for_hittest)
return true;
return false;
}
bool RenderWidgetHostInputEventRouter::HittestDelegate::AcceptHitTarget(
const cc::SurfaceDrawQuad* surface_quad,
const gfx::Point& point_in_quad_space) {
auto it = hittest_data_.find(surface_quad->surface_id);
if (it != hittest_data_.end() && !it->second.ignored_for_hittest)
return true;
return false;
}
RenderWidgetHostInputEventRouter::RenderWidgetHostInputEventRouter()
: 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) {}
RenderWidgetHostInputEventRouter::~RenderWidgetHostInputEventRouter() {
// We may be destroyed before some of the owners in the map, so we must
// remove ourself from their observer lists.
ClearAllObserverRegistrations();
}
RenderWidgetHostViewBase* RenderWidgetHostInputEventRouter::FindEventTarget(
RenderWidgetHostViewBase* root_view,
const gfx::Point& point,
gfx::Point* transformed_point) {
// Short circuit if owner_map has only one RenderWidgetHostView, no need for
// hit testing.
if (owner_map_.size() <= 1) {
*transformed_point = point;
return root_view;
}
// The hittest delegate is used to reject hittesting quads based on extra
// hittesting data send by the renderer.
HittestDelegate delegate(hittest_data_);
// The conversion of point to transform_point is done over the course of the
// hit testing, and reflect transformations that would normally be applied in
// the renderer process if the event was being routed between frames within a
// single process with only one RenderWidgetHost.
cc::FrameSinkId frame_sink_id =
root_view->FrameSinkIdAtPoint(&delegate, point, transformed_point);
const FrameSinkIdOwnerMap::iterator iter = owner_map_.find(frame_sink_id);
// If the point hit a Surface whose namspace is no longer in the map, then
// it likely means the RenderWidgetHostView has been destroyed but its
// parent frame has not sent a new compositor frame since that happened.
if (iter == owner_map_.end())
return root_view;
return iter->second;
}
void RenderWidgetHostInputEventRouter::RouteMouseEvent(
RenderWidgetHostViewBase* root_view,
blink::WebMouseEvent* event,
const ui::LatencyInfo& latency) {
RenderWidgetHostViewBase* target = nullptr;
gfx::Point transformed_point;
// When the mouse is locked, directly route the events to the widget that
// holds the lock and return.
if (root_view->IsMouseLocked()) {
target = RenderWidgetHostImpl::From(root_view->GetRenderWidgetHost())
->delegate()
->GetMouseLockWidget()
->GetView();
if (!root_view->TransformPointToCoordSpaceForView(
gfx::Point(event->PositionInWidget().x,
event->PositionInWidget().y),
target, &transformed_point))
return;
event->SetPositionInWidget(transformed_point.x(), transformed_point.y());
target->ProcessMouseEvent(*event, latency);
return;
}
const int mouse_button_modifiers = blink::WebInputEvent::kLeftButtonDown |
blink::WebInputEvent::kMiddleButtonDown |
blink::WebInputEvent::kRightButtonDown |
blink::WebInputEvent::kBackButtonDown |
blink::WebInputEvent::kForwardButtonDown;
if (mouse_capture_target_.target &&
event->GetType() != blink::WebInputEvent::kMouseDown &&
(event->GetType() == blink::WebInputEvent::kMouseUp ||
event->GetModifiers() & mouse_button_modifiers)) {
target = mouse_capture_target_.target;
if (!root_view->TransformPointToCoordSpaceForView(
gfx::Point(event->PositionInWidget().x,
event->PositionInWidget().y),
target, &transformed_point))
return;
if (event->GetType() == blink::WebInputEvent::kMouseUp)
mouse_capture_target_.target = nullptr;
} else {
target = FindEventTarget(
root_view,
gfx::Point(event->PositionInWidget().x, event->PositionInWidget().y),
&transformed_point);
}
// RenderWidgetHostViewGuest does not properly handle direct routing of mouse
// events, so they have to go by the double-hop forwarding path through
// the embedding renderer and then BrowserPluginGuest.
if (target && target->IsRenderWidgetHostViewGuest()) {
ui::LatencyInfo latency_info;
RenderWidgetHostViewBase* owner_view =
static_cast<RenderWidgetHostViewGuest*>(target)
->GetOwnerRenderWidgetHostView();
// In case there is nested RenderWidgetHostViewGuests (i.e., PDF inside
// <webview>), we will need the owner view of the top-most guest for input
// routing.
while (owner_view->IsRenderWidgetHostViewGuest()) {
owner_view = static_cast<RenderWidgetHostViewGuest*>(owner_view)
->GetOwnerRenderWidgetHostView();
}
if (owner_view != root_view) {
// This happens when the view is embedded inside a cross-process frame
// (i.e., owner view is a RenderWidgetHostViewChildFrame).
gfx::Point owner_point;
if (!root_view->TransformPointToCoordSpaceForView(
gfx::Point(event->PositionInWidget().x,
event->PositionInWidget().y),
owner_view, &owner_point)) {
return;
}
event->SetPositionInWidget(owner_point.x(), owner_point.y());
}
owner_view->ProcessMouseEvent(*event, latency_info);
return;
}
if (event->GetType() == blink::WebInputEvent::kMouseDown)
mouse_capture_target_.target = target;
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->GetType() == blink::WebInputEvent::kMouseLeave ||
event->GetType() == blink::WebInputEvent::kMouseMove) &&
target != last_mouse_move_target_)
SendMouseEnterOrLeaveEvents(event, target, root_view);
event->SetPositionInWidget(transformed_point.x(), transformed_point.y());
target->ProcessMouseEvent(*event, latency);
}
void RenderWidgetHostInputEventRouter::RouteMouseWheelEvent(
RenderWidgetHostViewBase* root_view,
blink::WebMouseWheelEvent* event,
const ui::LatencyInfo& latency) {
RenderWidgetHostViewBase* target = nullptr;
gfx::Point transformed_point;
if (root_view->IsMouseLocked()) {
target = RenderWidgetHostImpl::From(root_view->GetRenderWidgetHost())
->delegate()
->GetMouseLockWidget()
->GetView();
if (!root_view->TransformPointToCoordSpaceForView(
gfx::Point(event->PositionInWidget().x,
event->PositionInWidget().y),
target, &transformed_point))
return;
} else if (root_view->wheel_scroll_latching_enabled()) {
if (event->phase == blink::WebMouseWheelEvent::kPhaseBegan) {
wheel_target_.target = FindEventTarget(
root_view,
gfx::Point(event->PositionInWidget().x, event->PositionInWidget().y),
&transformed_point);
wheel_target_.delta =
transformed_point -
gfx::Point(event->PositionInWidget().x, event->PositionInWidget().y);
target = wheel_target_.target;
} else {
if (wheel_target_.target) {
target = wheel_target_.target;
transformed_point = gfx::Point(event->PositionInWidget().x,
event->PositionInWidget().y) +
wheel_target_.delta;
}
}
} else { // !root_view->IsMouseLocked() &&
// !root_view->wheel_scroll_latching_enabled()
target = FindEventTarget(
root_view,
gfx::Point(event->PositionInWidget().x, event->PositionInWidget().y),
&transformed_point);
}
if (!target)
return;
event->SetPositionInWidget(transformed_point.x(), transformed_point.y());
target->ProcessMouseWheelEvent(*event, latency);
DCHECK(root_view->wheel_scroll_latching_enabled() || !wheel_target_.target);
if (event->phase == blink::WebMouseWheelEvent::kPhaseEnded ||
event->momentum_phase == blink::WebMouseWheelEvent::kPhaseEnded) {
wheel_target_.target = nullptr;
}
}
void RenderWidgetHostInputEventRouter::RouteGestureEvent(
RenderWidgetHostViewBase* root_view,
blink::WebGestureEvent* event,
const ui::LatencyInfo& latency) {
if (event->IsTargetViewport()) {
root_view->ProcessGestureEvent(*event, latency);
return;
}
switch (event->source_device) {
case blink::kWebGestureDeviceUninitialized:
case blink::kWebGestureDeviceCount:
NOTREACHED() << "Uninitialized device type is not allowed";
break;
case blink::kWebGestureDeviceSyntheticAutoscroll:
NOTREACHED() << "Only target_viewport synthetic autoscrolls are "
"currently supported";
break;
case blink::kWebGestureDeviceTouchpad:
RouteTouchpadGestureEvent(root_view, event, latency);
break;
case blink::kWebGestureDeviceTouchscreen:
RouteTouchscreenGestureEvent(root_view, event, latency);
break;
};
}
namespace {
unsigned CountChangedTouchPoints(const blink::WebTouchEvent& event) {
unsigned changed_count = 0;
blink::WebTouchPoint::State required_state =
blink::WebTouchPoint::kStateUndefined;
switch (event.GetType()) {
case blink::WebInputEvent::kTouchStart:
required_state = blink::WebTouchPoint::kStatePressed;
break;
case blink::WebInputEvent::kTouchEnd:
required_state = blink::WebTouchPoint::kStateReleased;
break;
case blink::WebInputEvent::kTouchCancel:
required_state = blink::WebTouchPoint::kStateCancelled;
break;
default:
// We'll only ever call this method for TouchStart, TouchEnd
// and TounchCancel events, so mark the rest as not-reached.
NOTREACHED();
}
for (unsigned i = 0; i < event.touches_length; ++i) {
if (event.touches[i].state == required_state)
++changed_count;
}
DCHECK(event.GetType() == blink::WebInputEvent::kTouchCancel ||
changed_count == 1);
return changed_count;
}
} // namespace
void RenderWidgetHostInputEventRouter::RouteTouchEvent(
RenderWidgetHostViewBase* root_view,
blink::WebTouchEvent* event,
const ui::LatencyInfo& latency) {
switch (event->GetType()) {
case blink::WebInputEvent::kTouchStart: {
active_touches_ += CountChangedTouchPoints(*event);
if (active_touches_ == 1) {
// Since this is the first touch, it defines the target for the rest
// of this sequence.
DCHECK(!touch_target_.target);
gfx::Point transformed_point;
gfx::Point original_point(event->touches[0].PositionInWidget().x,
event->touches[0].PositionInWidget().y);
touch_target_.target =
FindEventTarget(root_view, original_point, &transformed_point);
// TODO(wjmaclean): Instead of just computing a delta, we should extract
// the complete transform. We assume it doesn't change for the duration
// of the touch sequence, though this could be wrong; a better approach
// might be to always transform each point to the |touch_target_.target|
// for the duration of the sequence.
touch_target_.delta = transformed_point - original_point;
touchscreen_gesture_target_queue_.push_back(touch_target_);
if (!touch_target_.target)
return;
if (touch_target_.target == bubbling_gesture_scroll_target_.target) {
SendGestureScrollEnd(
bubbling_gesture_scroll_target_.target,
DummyGestureScrollUpdate(event->TimeStampSeconds()));
CancelScrollBubbling(bubbling_gesture_scroll_target_.target);
}
}
if (touch_target_.target) {
TransformEventTouchPositions(event, touch_target_.delta);
touch_target_.target->ProcessTouchEvent(*event, latency);
}
break;
}
case blink::WebInputEvent::kTouchMove:
if (touch_target_.target) {
TransformEventTouchPositions(event, touch_target_.delta);
touch_target_.target->ProcessTouchEvent(*event, latency);
}
break;
case blink::WebInputEvent::kTouchEnd:
case blink::WebInputEvent::kTouchCancel:
// It might be safer to test active_touches_ and only decrement it if it's
// non-zero, since active_touches_ can be reset to 0 in
// OnRenderWidgetHostViewBaseDestroyed, and this can happen between the
// TouchStart and a subsequent TouchMove/End/Cancel.
DCHECK(active_touches_);
active_touches_ -= CountChangedTouchPoints(*event);
if (!touch_target_.target)
return;
TransformEventTouchPositions(event, touch_target_.delta);
touch_target_.target->ProcessTouchEvent(*event, latency);
if (!active_touches_)
touch_target_.target = nullptr;
break;
default:
NOTREACHED();
}
}
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->IsRenderWidgetHostViewChildFrame()) {
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) {
last_mouse_move_target_ = target;
last_mouse_move_root_view_ = root_view;
return;
}
entered_views.push_back(cur_view);
}
// Non-root RWHVs are guaranteed to be RenderWidgetHostViewChildFrames,
// as long as they are the only embeddable RWHVs.
DCHECK_EQ(cur_view, root_view);
cur_view = last_mouse_move_target_;
if (cur_view) {
exited_views.push_back(cur_view);
while (cur_view->IsRenderWidgetHostViewChildFrame()) {
cur_view = static_cast<RenderWidgetHostViewChildFrame*>(cur_view)
->GetParentView();
if (!cur_view) {
last_mouse_move_target_ = target;
last_mouse_move_root_view_ = root_view;
return;
}
exited_views.push_back(cur_view);
}
DCHECK_EQ(cur_view, root_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.SetType(blink::WebInputEvent::kMouseLeave);
// There is a chance of a race if the last target has recently created a
// new compositor surface. The SurfaceID for that might not have
// propagated to its embedding surface, which makes it impossible to
// compute the transformation for it
if (!root_view->TransformPointToCoordSpaceForView(
gfx::Point(event->PositionInWidget().x,
event->PositionInWidget().y),
view, &transformed_point))
transformed_point = gfx::Point();
mouse_leave.SetPositionInWidget(transformed_point.x(),
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.SetType(blink::WebInputEvent::kMouseMove);
if (!root_view->TransformPointToCoordSpaceForView(
gfx::Point(event->PositionInWidget().x,
event->PositionInWidget().y),
common_ancestor, &transformed_point))
transformed_point = gfx::Point();
mouse_move.SetPositionInWidget(transformed_point.x(),
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.SetType(blink::WebInputEvent::kMouseMove);
if (!root_view->TransformPointToCoordSpaceForView(
gfx::Point(event->PositionInWidget().x,
event->PositionInWidget().y),
view, &transformed_point))
transformed_point = gfx::Point();
mouse_enter.SetPositionInWidget(transformed_point.x(),
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) {
// TODO(kenrb, tdresser): This needs to be refactored when scroll latching
// is implemented (see https://crbug.com/526463). This design has some
// race problems that can result in lost scroll delta, which are very
// difficult to resolve until this is changed to do all scroll targeting,
// including bubbling, based on GestureScrollBegin.
DCHECK(target_view);
DCHECK(event.GetType() == blink::WebInputEvent::kGestureScrollUpdate ||
event.GetType() == blink::WebInputEvent::kGestureScrollEnd ||
event.GetType() == blink::WebInputEvent::kGestureFlingStart);
// DCHECK_XNOR the current and original bubble targets. Both should be set
// if a bubbling gesture scroll is in progress.
DCHECK(!first_bubbling_scroll_target_.target ==
!bubbling_gesture_scroll_target_.target);
ui::LatencyInfo latency_info =
ui::WebInputEventTraits::CreateLatencyInfoForWebGestureEvent(event);
// If target_view is already set up for bubbled scrolls, we forward
// the event to the current scroll target without further consideration.
if (target_view == first_bubbling_scroll_target_.target) {
bubbling_gesture_scroll_target_.target->ProcessGestureEvent(event,
latency_info);
if (event.GetType() == blink::WebInputEvent::kGestureScrollEnd ||
event.GetType() == blink::WebInputEvent::kGestureFlingStart) {
first_bubbling_scroll_target_.target = nullptr;
bubbling_gesture_scroll_target_.target = nullptr;
}
return;
}
// Disregard GestureScrollEnd events going to non-current targets.
// These should only happen on ACKs of synthesized GSE events that are
// sent from SendGestureScrollEnd calls, and are not relevant here.
if (event.GetType() == blink::WebInputEvent::kGestureScrollEnd)
return;
// This is a special case to catch races where multiple GestureScrollUpdates
// have been sent to a renderer before the first one was ACKed, and the ACK
// caused a bubble retarget. In this case they all get forwarded.
if (target_view == bubbling_gesture_scroll_target_.target) {
bubbling_gesture_scroll_target_.target->ProcessGestureEvent(event,
latency_info);
return;
}
// If target_view has unrelated gesture events in progress, do
// not proceed. This could cause confusion between independent
// scrolls.
if (target_view == touchscreen_gesture_target_.target ||
target_view == touchpad_gesture_target_.target ||
target_view == touch_target_.target)
return;
// This accounts for bubbling through nested OOPIFs. A gesture scroll has
// been bubbled but the target has sent back a gesture scroll event ack with
// unused scroll delta, and so another level of bubbling is needed. This
// requires a GestureScrollEnd be sent to the last view, which will no
// longer be the scroll target.
if (bubbling_gesture_scroll_target_.target)
SendGestureScrollEnd(bubbling_gesture_scroll_target_.target, event);
else
first_bubbling_scroll_target_.target = target_view;
bubbling_gesture_scroll_target_.target = target_view;
SendGestureScrollBegin(target_view, event);
target_view->ProcessGestureEvent(event, latency_info);
}
void RenderWidgetHostInputEventRouter::SendGestureScrollBegin(
RenderWidgetHostViewBase* view,
const blink::WebGestureEvent& event) {
DCHECK(event.GetType() == blink::WebInputEvent::kGestureScrollUpdate ||
event.GetType() == blink::WebInputEvent::kGesturePinchBegin);
blink::WebGestureEvent scroll_begin(event);
scroll_begin.SetType(blink::WebInputEvent::kGestureScrollBegin);
scroll_begin.data.scroll_begin.delta_x_hint =
event.data.scroll_update.delta_x;
scroll_begin.data.scroll_begin.delta_y_hint =
event.data.scroll_update.delta_y;
scroll_begin.data.scroll_begin.delta_hint_units =
event.data.scroll_update.delta_units;
view->ProcessGestureEvent(
scroll_begin,
ui::WebInputEventTraits::CreateLatencyInfoForWebGestureEvent(event));
}
void RenderWidgetHostInputEventRouter::SendGestureScrollEnd(
RenderWidgetHostViewBase* view,
const blink::WebGestureEvent& event) {
DCHECK(event.GetType() == blink::WebInputEvent::kGestureScrollUpdate ||
event.GetType() == blink::WebInputEvent::kGesturePinchEnd);
blink::WebGestureEvent scroll_end(event);
scroll_end.SetType(blink::WebInputEvent::kGestureScrollEnd);
scroll_end.SetTimeStampSeconds(
(base::TimeTicks::Now() - base::TimeTicks()).InSecondsF());
scroll_end.data.scroll_end.inertial_phase =
event.data.scroll_update.inertial_phase;
scroll_end.data.scroll_end.delta_units = event.data.scroll_update.delta_units;
view->ProcessGestureEvent(
scroll_end,
ui::WebInputEventTraits::CreateLatencyInfoForWebGestureEvent(event));
}
void RenderWidgetHostInputEventRouter::CancelScrollBubbling(
RenderWidgetHostViewBase* target_view) {
DCHECK(target_view);
if (target_view == first_bubbling_scroll_target_.target) {
first_bubbling_scroll_target_.target = nullptr;
bubbling_gesture_scroll_target_.target = nullptr;
}
}
void RenderWidgetHostInputEventRouter::AddFrameSinkIdOwner(
const cc::FrameSinkId& id,
RenderWidgetHostViewBase* owner) {
DCHECK(owner_map_.find(id) == owner_map_.end());
// We want to be notified if the owner is destroyed so we can remove it from
// our map.
owner->AddObserver(this);
owner_map_.insert(std::make_pair(id, owner));
}
void RenderWidgetHostInputEventRouter::RemoveFrameSinkIdOwner(
const cc::FrameSinkId& id) {
auto it_to_remove = owner_map_.find(id);
if (it_to_remove != owner_map_.end()) {
it_to_remove->second->RemoveObserver(this);
owner_map_.erase(it_to_remove);
}
for (auto it = hittest_data_.begin(); it != hittest_data_.end();) {
if (it->first.frame_sink_id() == id)
it = hittest_data_.erase(it);
else
++it;
}
}
void RenderWidgetHostInputEventRouter::OnHittestData(
const FrameHostMsg_HittestData_Params& params) {
if (owner_map_.find(params.surface_id.frame_sink_id()) == owner_map_.end()) {
return;
}
HittestData data;
data.ignored_for_hittest = params.ignored_for_hittest;
hittest_data_[params.surface_id] = data;
}
RenderWidgetHostImpl*
RenderWidgetHostInputEventRouter::GetRenderWidgetHostAtPoint(
RenderWidgetHostViewBase* root_view,
const gfx::Point& point,
gfx::Point* transformed_point) {
if (!root_view)
return nullptr;
return RenderWidgetHostImpl::From(
FindEventTarget(root_view, point, transformed_point)
->GetRenderWidgetHost());
}
void RenderWidgetHostInputEventRouter::RouteTouchscreenGestureEvent(
RenderWidgetHostViewBase* root_view,
blink::WebGestureEvent* event,
const ui::LatencyInfo& latency) {
DCHECK_EQ(blink::kWebGestureDeviceTouchscreen, event->source_device);
if (event->GetType() == blink::WebInputEvent::kGesturePinchBegin) {
in_touchscreen_gesture_pinch_ = true;
// If the root view wasn't already receiving the gesture stream, then we
// need to wrap the diverted pinch events in a GestureScrollBegin/End.
// TODO(wjmaclean,kenrb,tdresser): When scroll latching lands, we can
// revisit how this code should work.
// https://crbug.com/526463
auto* rwhi =
static_cast<RenderWidgetHostImpl*>(root_view->GetRenderWidgetHost());
// If the root view is the current gesture target, then we explicitly don't
// send a GestureScrollBegin, as by the time we see GesturePinchBegin there
// should have been one.
if (root_view != touchscreen_gesture_target_.target &&
!rwhi->is_in_touchscreen_gesture_scroll()) {
gesture_pinch_did_send_scroll_begin_ = true;
SendGestureScrollBegin(root_view, *event);
}
}
if (in_touchscreen_gesture_pinch_) {
root_view->ProcessGestureEvent(*event, latency);
if (event->GetType() == blink::WebInputEvent::kGesturePinchEnd) {
in_touchscreen_gesture_pinch_ = false;
// If the root view wasn't already receiving the gesture stream, then we
// need to wrap the diverted pinch events in a GestureScrollBegin/End.
auto* rwhi =
static_cast<RenderWidgetHostImpl*>(root_view->GetRenderWidgetHost());
if (root_view != touchscreen_gesture_target_.target &&
gesture_pinch_did_send_scroll_begin_ &&
rwhi->is_in_touchscreen_gesture_scroll()) {
SendGestureScrollEnd(root_view, *event);
}
gesture_pinch_did_send_scroll_begin_ = false;
}
return;
}
// On Android it is possible for touchscreen gesture events to arrive that
// are not associated with touch events, because non-synthetic events can be
// created by ContentView. In that case the target queue will be empty.
if (touchscreen_gesture_target_queue_.empty()) {
gfx::Point transformed_point;
gfx::Point original_point(event->x, event->y);
touchscreen_gesture_target_.target =
FindEventTarget(root_view, original_point, &transformed_point);
touchscreen_gesture_target_.delta = transformed_point - original_point;
} else if (event->GetType() == blink::WebInputEvent::kGestureTapDown) {
// We use GestureTapDown to detect the start of a gesture sequence since
// there is no WebGestureEvent equivalent for ET_GESTURE_BEGIN. Note that
// this means the GestureFlingCancel that always comes between
// ET_GESTURE_BEGIN and GestureTapDown is sent to the previous target, in
// case it is still in a fling.
touchscreen_gesture_target_ = touchscreen_gesture_target_queue_.front();
touchscreen_gesture_target_queue_.pop_front();
// Abort any scroll bubbling in progress to avoid double entry.
if (touchscreen_gesture_target_.target &&
touchscreen_gesture_target_.target ==
bubbling_gesture_scroll_target_.target) {
SendGestureScrollEnd(bubbling_gesture_scroll_target_.target,
DummyGestureScrollUpdate(event->TimeStampSeconds()));
CancelScrollBubbling(bubbling_gesture_scroll_target_.target);
}
}
if (!touchscreen_gesture_target_.target)
return;
// TODO(mohsen): Add tests to check event location.
event->x += touchscreen_gesture_target_.delta.x();
event->y += touchscreen_gesture_target_.delta.y();
touchscreen_gesture_target_.target->ProcessGestureEvent(*event, latency);
}
void RenderWidgetHostInputEventRouter::RouteTouchpadGestureEvent(
RenderWidgetHostViewBase* root_view,
blink::WebGestureEvent* event,
const ui::LatencyInfo& latency) {
DCHECK_EQ(blink::kWebGestureDeviceTouchpad, event->source_device);
if (event->GetType() == blink::WebInputEvent::kGesturePinchBegin ||
event->GetType() == blink::WebInputEvent::kGestureFlingStart) {
gfx::Point transformed_point;
gfx::Point original_point(event->x, event->y);
touchpad_gesture_target_.target =
FindEventTarget(root_view, original_point, &transformed_point);
// TODO(mohsen): Instead of just computing a delta, we should extract the
// complete transform. We assume it doesn't change for the duration of the
// touchpad gesture sequence, though this could be wrong; a better approach
// might be to always transform each point to the
// |touchpad_gesture_target_.target| for the duration of the sequence.
touchpad_gesture_target_.delta = transformed_point - original_point;
// Abort any scroll bubbling in progress to avoid double entry.
if (touchpad_gesture_target_.target &&
touchpad_gesture_target_.target ==
bubbling_gesture_scroll_target_.target) {
SendGestureScrollEnd(bubbling_gesture_scroll_target_.target,
DummyGestureScrollUpdate(event->TimeStampSeconds()));
CancelScrollBubbling(bubbling_gesture_scroll_target_.target);
}
}
if (!touchpad_gesture_target_.target)
return;
// TODO(mohsen): Add tests to check event location.
event->x += touchpad_gesture_target_.delta.x();
event->y += touchpad_gesture_target_.delta.y();
touchpad_gesture_target_.target->ProcessGestureEvent(*event, latency);
}
std::vector<RenderWidgetHostView*>
RenderWidgetHostInputEventRouter::GetRenderWidgetHostViewsForTests() const {
std::vector<RenderWidgetHostView*> hosts;
for (auto entry : owner_map_)
hosts.push_back(entry.second);
return hosts;
}
} // namespace content