blob: b5e4b7b5394a49460db4f2a3f782efa7c0b97d47 [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 <algorithm>
#include <deque>
#include <vector>
#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/stringprintf.h"
#include "components/viz/common/features.h"
#include "components/viz/common/hit_test/hit_test_region_list.h"
#include "components/viz/common/quads/surface_draw_quad.h"
#include "components/viz/host/host_frame_sink_manager.h"
#include "components/viz/service/surfaces/surface_manager.h"
#include "content/browser/compositor/surface_utils.h"
#include "content/browser/frame_host/render_widget_host_view_guest.h"
#include "content/browser/renderer_host/cursor_manager.h"
#include "content/browser/renderer_host/input/touch_emulator.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_base.h"
#include "content/browser/renderer_host/render_widget_host_view_child_frame.h"
#include "content/common/frame_messages.h"
#include "third_party/blink/public/platform/web_input_event.h"
#include "ui/base/layout.h"
#include "ui/gfx/geometry/dip_util.h"
namespace {
// Transforms WebTouchEvent touch positions from the root view coordinate
// space to the target view coordinate space.
void TransformEventTouchPositions(blink::WebTouchEvent* event,
const gfx::Transform& transform) {
for (unsigned i = 0; i < event->touches_length; ++i) {
gfx::PointF point(event->touches[i].PositionInWidget());
transform.TransformPoint(&point);
event->touches[i].SetPositionInWidget(point);
}
}
blink::WebGestureEvent DummyGestureScrollUpdate(base::TimeTicks time_stamp) {
return blink::WebGestureEvent(blink::WebInputEvent::kGestureScrollUpdate,
blink::WebInputEvent::kNoModifiers, time_stamp);
}
gfx::PointF ComputePointInRootInPixels(
const gfx::PointF& point,
content::RenderWidgetHostViewBase* root_view,
float device_scale_factor) {
gfx::PointF point_in_root = point;
root_view->TransformPointToRootSurface(&point_in_root);
return gfx::ConvertPointToPixel(device_scale_factor, point_in_root);
}
bool IsMouseButtonDown(const blink::WebMouseEvent& event) {
constexpr int mouse_button_modifiers =
blink::WebInputEvent::kLeftButtonDown |
blink::WebInputEvent::kMiddleButtonDown |
blink::WebInputEvent::kRightButtonDown |
blink::WebInputEvent::kBackButtonDown |
blink::WebInputEvent::kForwardButtonDown;
return event.GetModifiers() & mouse_button_modifiers;
}
} // anonymous namespace
namespace content {
// Helper method also used from hit_test_debug_key_event_observer.cc
viz::HitTestQuery* GetHitTestQuery(
viz::HostFrameSinkManager* host_frame_sink_manager,
const viz::FrameSinkId& frame_sink_id) {
if (!frame_sink_id.is_valid())
return nullptr;
const auto& display_hit_test_query_map =
host_frame_sink_manager->display_hit_test_query();
const auto iter = display_hit_test_query_map.find(frame_sink_id);
if (iter == display_hit_test_query_map.end())
return nullptr;
return iter->second.get();
}
// A class to implement a queue for tracking outbound TouchEvents, and making
// sure that their acks are returned to the appropriate root view in order.
// This is important to ensure proper operation of the GestureProvider.
// Some challenges include:
// * differentiating between native and emulated TouchEvents, as the latter ack
// to the TouchEmulator's GestureProvider,
// * making sure all events from destroyed renderers are acked properly, and
// without delaying acks from other renderers, and
// * making sure events are only acked if the root_view (at the time of the
// out-bound event) is still valid.
// Some of this logic, e.g. the last item above, is shared with
// RenderWidgetHostViewBase.
class TouchEventAckQueue {
public:
enum class TouchEventAckStatus { TouchEventNotAcked, TouchEventAcked };
enum class TouchEventSource { SystemTouchEvent, EmulatedTouchEvent };
struct AckData {
uint32_t touch_event_id;
RenderWidgetHostViewBase* target_view;
RenderWidgetHostViewBase* root_view;
TouchEventSource touch_event_source;
TouchEventAckStatus touch_event_ack_status;
InputEventAckState ack_result;
};
TouchEventAckQueue() {}
void Add(uint32_t touch_event_id,
RenderWidgetHostViewBase* target_view,
RenderWidgetHostViewBase* root_view,
TouchEventSource touch_event_source,
TouchEventAckStatus touch_event_ack_status,
InputEventAckState ack_result);
void Add(uint32_t touch_event_id,
RenderWidgetHostViewBase* target_view,
RenderWidgetHostViewBase* root_view,
TouchEventSource touch_event_source);
void MarkAcked(uint32_t touch_event_id,
InputEventAckState ack_result,
RenderWidgetHostViewBase* target_view);
void UpdateQueueAfterTargetDestroyed(RenderWidgetHostViewBase* target_view);
private:
void ProcessAckedTouchEvents();
void ReportTouchEventAckQueueUmaStats();
std::deque<AckData> ack_queue_;
};
void TouchEventAckQueue::Add(uint32_t touch_event_id,
RenderWidgetHostViewBase* target_view,
RenderWidgetHostViewBase* root_view,
TouchEventSource touch_event_source,
TouchEventAckStatus touch_event_ack_status,
InputEventAckState ack_result) {
AckData data = {
touch_event_id, target_view, root_view, touch_event_source,
touch_event_ack_status, ack_result};
ack_queue_.push_back(data);
if (touch_event_ack_status == TouchEventAckStatus::TouchEventAcked)
ProcessAckedTouchEvents();
ReportTouchEventAckQueueUmaStats();
}
void TouchEventAckQueue::Add(uint32_t touch_event_id,
RenderWidgetHostViewBase* target_view,
RenderWidgetHostViewBase* root_view,
TouchEventSource touch_event_source) {
Add(touch_event_id, target_view, root_view, touch_event_source,
TouchEventAckStatus::TouchEventNotAcked, INPUT_EVENT_ACK_STATE_UNKNOWN);
}
void TouchEventAckQueue::MarkAcked(uint32_t touch_event_id,
InputEventAckState ack_result,
RenderWidgetHostViewBase* target_view) {
auto it = find_if(ack_queue_.begin(), ack_queue_.end(),
[touch_event_id](AckData data) {
return data.touch_event_id == touch_event_id;
});
if (it == ack_queue_.end())
return;
DCHECK(it->touch_event_ack_status != TouchEventAckStatus::TouchEventAcked);
DCHECK(target_view && target_view == it->target_view);
it->touch_event_ack_status = TouchEventAckStatus::TouchEventAcked;
it->ack_result = ack_result;
ProcessAckedTouchEvents();
}
void TouchEventAckQueue::ProcessAckedTouchEvents() {
if (ack_queue_.empty())
return;
// TODO(wjmaclean): modify the following loop to actually forward the acks
// to the root_view. Must verify that the root_view at the time the event
// was registered still exists.
while (!ack_queue_.empty() && ack_queue_.front().touch_event_ack_status ==
TouchEventAckStatus::TouchEventAcked) {
// TODO(wjmaclean): We will eventually ack touch events to the root_view
// here. Each ack will require confirmation that the touch event's root
// view (at the time of event dispatch) is still valid, otherwise we just
// discard the ack.
ack_queue_.pop_front();
}
}
void TouchEventAckQueue::ReportTouchEventAckQueueUmaStats() {
size_t count = ack_queue_.size();
UMA_HISTOGRAM_COUNTS_10000("Event.FrameEventRouting.TouchEventAckQueueSize",
count);
// TODO(wjmaclean): is it worth also recording how many different renderers
// are waiting on touch event acks at the time of reporting?
}
void TouchEventAckQueue::UpdateQueueAfterTargetDestroyed(
RenderWidgetHostViewBase* target_view) {
// If a queue entry's root view is being destroyed, just delete it.
ack_queue_.erase(remove_if(ack_queue_.begin(), ack_queue_.end(),
[target_view](AckData data) {
return data.root_view == target_view;
}),
ack_queue_.end());
// Otherwise, mark its status accordingly.
for_each(ack_queue_.begin(), ack_queue_.end(), [target_view](AckData data) {
if (data.target_view == target_view) {
data.touch_event_ack_status = TouchEventAckStatus::TouchEventAcked;
data.ack_result = INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS;
}
});
ProcessAckedTouchEvents();
}
void RenderWidgetHostInputEventRouter::OnRenderWidgetHostViewBaseDestroyed(
RenderWidgetHostViewBase* view) {
// RenderWidgetHostViewBase::RemoveObserver() should only ever be called
// in this function, except during the shutdown of this class. This prevents
// removal of an observed view that is being tracked as an event target
// without cleaning up dangling pointers to it.
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 (touch_emulator_)
touch_emulator_->OnViewDestroyed(view);
if (view == touch_target_.target) {
touch_target_.target = nullptr;
active_touches_ = 0;
}
touch_event_ack_queue_->UpdateQueueAfterTargetDestroyed(view);
if (view == wheel_target_.target)
wheel_target_.target = nullptr;
// If the target that's being destroyed is in the gesture target map, we
// replace it with nullptr so that we maintain the 1:1 correspondence between
// map entries and the touch sequences that underly them.
for (auto it : touchscreen_gesture_target_map_) {
if (it.second.target == view)
it.second.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) {
bubbling_gesture_scroll_target_.target = nullptr;
first_bubbling_scroll_target_.target = nullptr;
} else if (view == first_bubbling_scroll_target_.target) {
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_) {
DCHECK(last_mouse_move_target_->IsRenderWidgetHostViewChildFrame());
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;
}
if (view == last_fling_start_target_)
last_fling_start_target_ = nullptr;
event_targeter_->ViewWillBeDestroyed(view);
}
void RenderWidgetHostInputEventRouter::ClearAllObserverRegistrations() {
// Since we're shutting down, it's safe to call RenderWidgetHostViewBase::
// RemoveObserver() directly here.
for (auto entry : owner_map_)
entry.second->RemoveObserver(this);
owner_map_.clear();
}
RenderWidgetHostInputEventRouter::HittestDelegate::HittestDelegate(
const std::unordered_map<viz::SurfaceId, HittestData, viz::SurfaceIdHash>&
hittest_data)
: hittest_data_(hittest_data) {}
bool RenderWidgetHostInputEventRouter::HittestDelegate::RejectHitTarget(
const viz::SurfaceDrawQuad* surface_quad,
const gfx::Point& point_in_quad_space) {
auto it = hittest_data_.find(surface_quad->surface_range.end());
if (it != hittest_data_.end() && it->second.ignored_for_hittest)
return true;
return false;
}
bool RenderWidgetHostInputEventRouter::HittestDelegate::AcceptHitTarget(
const viz::SurfaceDrawQuad* surface_quad,
const gfx::Point& point_in_quad_space) {
auto it = hittest_data_.find(surface_quad->surface_range.end());
if (it != hittest_data_.end() && !it->second.ignored_for_hittest)
return true;
return false;
}
RenderWidgetHostInputEventRouter::RenderWidgetHostInputEventRouter()
: touchscreen_gesture_target_in_map_(false),
last_mouse_move_target_(nullptr),
last_mouse_move_root_view_(nullptr),
last_emulated_event_root_view_(nullptr),
last_device_scale_factor_(1.f),
active_touches_(0),
in_touchscreen_gesture_pinch_(false),
gesture_pinch_did_send_scroll_begin_(false),
event_targeter_(std::make_unique<RenderWidgetTargeter>(this)),
use_viz_hit_test_(features::IsVizHitTestingEnabled()),
touch_event_ack_queue_(new TouchEventAckQueue),
weak_ptr_factory_(this) {}
RenderWidgetHostInputEventRouter::~RenderWidgetHostInputEventRouter() {
// We may be destroyed before some of the owners in the map, so we must
// remove ourself from their observer lists.
ClearAllObserverRegistrations();
}
RenderWidgetTargetResult RenderWidgetHostInputEventRouter::FindMouseEventTarget(
RenderWidgetHostViewBase* root_view,
const blink::WebMouseEvent& event) const {
RenderWidgetHostViewBase* target = nullptr;
bool needs_transform_point = true;
bool latched_target = true;
bool should_verify_result = false;
if (root_view->IsMouseLocked()) {
target = root_view->host()->delegate()->GetMouseLockWidget()->GetView();
}
// Ignore mouse_capture_target_ if there are no mouse buttons currently down
// because this is only for the purpose of dragging.
if (!target && mouse_capture_target_.target &&
(event.GetType() == blink::WebInputEvent::kMouseUp ||
IsMouseButtonDown(event))) {
target = mouse_capture_target_.target;
}
gfx::PointF transformed_point;
if (!target) {
latched_target = false;
auto result = FindViewAtLocation(
root_view, event.PositionInWidget(), event.PositionInScreen(),
viz::EventSource::MOUSE, &transformed_point);
// Due to performance concerns we do not verify mouse move events.
should_verify_result = (event.GetType() == blink::WebInputEvent::kMouseMove)
? false
: result.should_verify_result;
if (result.should_query_view) {
DCHECK(!should_verify_result);
return {result.view, true, transformed_point, latched_target,
should_verify_result};
}
target = result.view;
// |transformed_point| is already transformed.
needs_transform_point = false;
}
if (needs_transform_point) {
if (!root_view->TransformPointToCoordSpaceForView(
event.PositionInWidget(), target, &transformed_point,
viz::EventSource::MOUSE)) {
return {nullptr, false, base::nullopt, latched_target, false};
}
}
return {target, false, transformed_point, latched_target,
should_verify_result};
}
RenderWidgetTargetResult
RenderWidgetHostInputEventRouter::FindMouseWheelEventTarget(
RenderWidgetHostViewBase* root_view,
const blink::WebMouseWheelEvent& event) const {
RenderWidgetHostViewBase* target = nullptr;
gfx::PointF transformed_point;
if (root_view->IsMouseLocked()) {
target = root_view->host()->delegate()->GetMouseLockWidget()->GetView();
if (!root_view->TransformPointToCoordSpaceForView(
event.PositionInWidget(), target, &transformed_point,
viz::EventSource::MOUSE)) {
return {nullptr, false, base::nullopt, true, false};
}
return {target, false, transformed_point, true, false};
}
if (event.phase == blink::WebMouseWheelEvent::kPhaseBegan) {
auto result = FindViewAtLocation(
root_view, event.PositionInWidget(), event.PositionInScreen(),
viz::EventSource::MOUSE, &transformed_point);
return {result.view, result.should_query_view, transformed_point, false,
result.should_verify_result};
}
// For non-begin events, the target found for the previous phaseBegan is
// used.
return {nullptr, false, base::nullopt, true, false};
}
RenderWidgetTargetResult RenderWidgetHostInputEventRouter::FindViewAtLocation(
RenderWidgetHostViewBase* root_view,
const gfx::PointF& point,
const gfx::PointF& point_in_screen,
viz::EventSource source,
gfx::PointF* transformed_point) const {
// 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, false, *transformed_point, false, false};
}
viz::FrameSinkId frame_sink_id;
bool query_renderer = false;
bool should_verify_result = false;
if (use_viz_hit_test_) {
viz::HitTestQuery* query = GetHitTestQuery(GetHostFrameSinkManager(),
root_view->GetRootFrameSinkId());
if (!query)
return {root_view, false, base::nullopt, false, false};
// |point_in_screen| is in the coordinate space of of the screen, but the
// display HitTestQuery does a hit test in the coordinate space of the root
// window. The following translation should account for that discrepancy.
// TODO(riajiang): Get rid of |point_in_screen| since it's not used.
float device_scale_factor = root_view->GetDeviceScaleFactor();
DCHECK_GT(device_scale_factor, 0.0f);
gfx::PointF point_in_root_in_pixels =
ComputePointInRootInPixels(point, root_view, device_scale_factor);
viz::Target target =
query->FindTargetForLocation(source, point_in_root_in_pixels);
frame_sink_id = target.frame_sink_id;
if (frame_sink_id.is_valid()) {
*transformed_point = gfx::ConvertPointToDIP(device_scale_factor,
target.location_in_target);
} else {
*transformed_point = point;
}
// To ensure the correctness of viz hit testing with cc generated data, we
// verify hit test results when:
// a) We use cc generated data to do synchronous hit testing and
// b) We use HitTestQuery to find the target (instead of reusing previous
// targets when hit testing latched events) and
// c) We are not hit testing MouseMove events which is too frequent to
// verify it without impacting performance.
// The code that implements c) locates in |FindMouseEventTarget|.
if (target.flags & viz::HitTestRegionFlags::kHitTestAsk)
query_renderer = true;
else if (features::IsVizHitTestingSurfaceLayerEnabled())
should_verify_result = true;
} else {
// 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.
frame_sink_id = root_view->FrameSinkIdAtPoint(
&delegate, point, transformed_point, &query_renderer);
}
auto* view = FindViewFromFrameSinkId(frame_sink_id);
// Send the event to |root_view| if |view| is not in |root_view|'s sub-tree
// anymore.
if (!view || (RenderWidgetHostViewGuest::GetRootView(view) != root_view)) {
view = root_view;
*transformed_point = point;
}
return {view, query_renderer, *transformed_point, false,
should_verify_result};
}
void RenderWidgetHostInputEventRouter::RouteMouseEvent(
RenderWidgetHostViewBase* root_view,
blink::WebMouseEvent* event,
const ui::LatencyInfo& latency) {
event_targeter_->FindTargetAndDispatch(root_view, *event, latency);
}
void RenderWidgetHostInputEventRouter::DispatchMouseEvent(
RenderWidgetHostViewBase* root_view,
RenderWidgetHostViewBase* target,
const blink::WebMouseEvent& mouse_event,
const ui::LatencyInfo& latency,
const base::Optional<gfx::PointF>& target_location) {
// TODO(wjmaclean): Should we be sending a no-consumer ack to the root_view
// if there is no target?
if (!target)
return;
// Implicitly release any capture when a MouseUp arrives, so that if any
// events arrive before the renderer can explicitly release capture, we can
// target those correctly. This also releases if there are no mouse buttons
// down, which is to protect against problems that can occur on some
// platforms where MouseUps are not received when the mouse cursor is off the
// browser window.
// Also, this is strictly necessary for touch emulation.
if (mouse_event.GetType() == blink::WebInputEvent::kMouseUp ||
!IsMouseButtonDown(mouse_event))
mouse_capture_target_.target = nullptr;
// When touch emulation is active, mouse events have to act like touch
// events, which requires that there be implicit capture between MouseDown
// and MouseUp.
if (mouse_event.GetType() == blink::WebInputEvent::kMouseDown &&
touch_emulator_ && touch_emulator_->enabled()) {
mouse_capture_target_.target = target;
}
DCHECK(target_location.has_value());
blink::WebMouseEvent event = mouse_event;
event.SetPositionInWidget(target_location->x(), target_location->y());
// SendMouseEnterOrLeaveEvents is called with the original event
// coordinates, which are transformed independently for each view that will
// receive an event. Also, since the view under the mouse has changed,
// notify the CursorManager that it might need to change the cursor.
if ((event.GetType() == blink::WebInputEvent::kMouseLeave ||
event.GetType() == blink::WebInputEvent::kMouseMove) &&
target != last_mouse_move_target_ && !root_view->IsMouseLocked()) {
SendMouseEnterOrLeaveEvents(mouse_event, target, root_view);
if (root_view->GetCursorManager())
root_view->GetCursorManager()->UpdateViewUnderCursor(target);
}
target->ProcessMouseEvent(event, latency);
}
void RenderWidgetHostInputEventRouter::RouteMouseWheelEvent(
RenderWidgetHostViewBase* root_view,
blink::WebMouseWheelEvent* event,
const ui::LatencyInfo& latency) {
event_targeter_->FindTargetAndDispatch(root_view, *event, latency);
}
void RenderWidgetHostInputEventRouter::DispatchMouseWheelEvent(
RenderWidgetHostViewBase* root_view,
RenderWidgetHostViewBase* target,
const blink::WebMouseWheelEvent& mouse_wheel_event,
const ui::LatencyInfo& latency,
const base::Optional<gfx::PointF>& target_location) {
base::Optional<gfx::PointF> point_in_target = target_location;
if (!root_view->IsMouseLocked()) {
if (mouse_wheel_event.phase == blink::WebMouseWheelEvent::kPhaseBegan) {
wheel_target_.target = target;
if (target_location.has_value()) {
wheel_target_.delta =
target_location.value() - mouse_wheel_event.PositionInWidget();
}
} else {
if (wheel_target_.target) {
DCHECK(!target && !target_location.has_value());
target = wheel_target_.target;
point_in_target.emplace(mouse_wheel_event.PositionInWidget() +
wheel_target_.delta);
} else if ((mouse_wheel_event.phase ==
blink::WebMouseWheelEvent::kPhaseEnded ||
mouse_wheel_event.momentum_phase ==
blink::WebMouseWheelEvent::kPhaseEnded) &&
bubbling_gesture_scroll_target_.target) {
// Send a GSE to the bubbling target and cancel scroll bubbling since
// the wheel target view is destroyed and the wheel end event won't get
// processed.
blink::WebGestureEvent fake_scroll_update =
DummyGestureScrollUpdate(mouse_wheel_event.TimeStamp());
fake_scroll_update.SetSourceDevice(blink::kWebGestureDeviceTouchpad);
SendGestureScrollEnd(bubbling_gesture_scroll_target_.target,
fake_scroll_update);
bubbling_gesture_scroll_target_.target = nullptr;
first_bubbling_scroll_target_.target = nullptr;
}
}
}
if (!target) {
root_view->WheelEventAck(mouse_wheel_event,
INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
return;
}
// If target_location doesn't have a value, it can be for two reasons:
// 1. |target| is null, in which case we would have early returned from the
// check above.
// 2. The event we are receiving is not a phaseBegan, in which case we should
// have got a valid |point_in_target| from wheel_target_.delta above.
DCHECK(point_in_target.has_value());
blink::WebMouseWheelEvent event = mouse_wheel_event;
event.SetPositionInWidget(point_in_target->x(), point_in_target->y());
target->ProcessMouseWheelEvent(event, latency);
if (mouse_wheel_event.phase == blink::WebMouseWheelEvent::kPhaseEnded ||
mouse_wheel_event.momentum_phase ==
blink::WebMouseWheelEvent::kPhaseEnded) {
wheel_target_.target = nullptr;
}
}
void RenderWidgetHostInputEventRouter::RouteGestureEvent(
RenderWidgetHostViewBase* root_view,
const blink::WebGestureEvent* event,
const ui::LatencyInfo& latency) {
if (event->IsTargetViewport()) {
root_view->ProcessGestureEvent(*event, latency);
return;
}
switch (event->SourceDevice()) {
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
// Any time a touch start event is handled/consumed/default prevented it is
// removed from the gesture map, because it will never create a gesture.
void RenderWidgetHostInputEventRouter::OnHandledTouchStartOrFirstTouchMove(
uint32_t unique_touch_event_id) {
// unique_touch_event_id of 0 implies a gesture not created by a touch.
DCHECK_NE(unique_touch_event_id, 0U);
touchscreen_gesture_target_map_.erase(unique_touch_event_id);
}
RenderWidgetTargetResult RenderWidgetHostInputEventRouter::FindTouchEventTarget(
RenderWidgetHostViewBase* root_view,
const blink::WebTouchEvent& event) {
// Tests may call this without an initial TouchStart, so check event type
// explicitly here.
if (active_touches_ || event.GetType() != blink::WebInputEvent::kTouchStart)
return {nullptr, false, base::nullopt, true, false};
active_touches_ += CountChangedTouchPoints(event);
gfx::PointF original_point = gfx::PointF(event.touches[0].PositionInWidget());
gfx::PointF original_point_in_screen(event.touches[0].PositionInScreen());
gfx::PointF transformed_point;
return FindViewAtLocation(root_view, original_point, original_point_in_screen,
viz::EventSource::TOUCH, &transformed_point);
}
void RenderWidgetHostInputEventRouter::DispatchTouchEvent(
RenderWidgetHostViewBase* root_view,
RenderWidgetHostViewBase* target,
const blink::WebTouchEvent& touch_event,
const ui::LatencyInfo& latency,
const base::Optional<gfx::PointF>& target_location,
bool is_emulated_touchevent) {
DCHECK(blink::WebInputEvent::IsTouchEventType(touch_event.GetType()) &&
touch_event.GetType() != blink::WebInputEvent::kTouchScrollStarted);
bool is_sequence_start = !touch_target_.target && target;
if (is_sequence_start) {
touch_target_.target = target;
// For now we only compute the transform at TouchStart, but in a follow-on
// CL this will be computed for all events in order to account for css
// animations, pinches, etc.
if (!root_view->GetTransformToViewCoordSpace(touch_target_.target,
&touch_target_.transform)) {
// Fall-back to just using the delta if we are unable to get the full
// transform.
touch_target_.transform.MakeIdentity();
if (target_location.has_value()) {
touch_target_.transform.Translate(
target_location.value() -
touch_event.touches[0].PositionInWidget());
}
}
DCHECK(touchscreen_gesture_target_map_.find(
touch_event.unique_touch_event_id) ==
touchscreen_gesture_target_map_.end());
touchscreen_gesture_target_map_[touch_event.unique_touch_event_id] =
touch_target_;
} else if (touch_event.GetType() == blink::WebInputEvent::kTouchStart) {
active_touches_ += CountChangedTouchPoints(touch_event);
}
// Test active_touches_ before decrementing, since its value can be
// reset to 0 in OnRenderWidgetHostViewBaseDestroyed, and this can
// happen between the TouchStart and a subsequent TouchMove/End/Cancel.
if ((touch_event.GetType() == blink::WebInputEvent::kTouchEnd ||
touch_event.GetType() == blink::WebInputEvent::kTouchCancel) &&
active_touches_) {
active_touches_ -= CountChangedTouchPoints(touch_event);
}
DCHECK_GE(active_touches_, 0);
// Debugging for crbug.com/814674.
if (touch_target_.target && !IsViewInMap(touch_target_.target)) {
NOTREACHED() << "Touch events should not be routed to a destroyed target "
"View.";
touch_target_.target = nullptr;
base::debug::DumpWithoutCrashing();
}
TouchEventAckQueue::TouchEventSource event_source =
is_emulated_touchevent
? TouchEventAckQueue::TouchEventSource::EmulatedTouchEvent
: TouchEventAckQueue::TouchEventSource::SystemTouchEvent;
if (!touch_target_.target) {
touch_event_ack_queue_->Add(
touch_event.unique_touch_event_id, nullptr, root_view, event_source,
TouchEventAckQueue::TouchEventAckStatus::TouchEventAcked,
INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
TouchEventWithLatencyInfo touch_with_latency(touch_event, latency);
root_view->ProcessAckedTouchEvent(touch_with_latency,
INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
return;
}
if (is_sequence_start) {
if (touch_target_.target == bubbling_gesture_scroll_target_.target) {
SendGestureScrollEnd(bubbling_gesture_scroll_target_.target,
DummyGestureScrollUpdate(touch_event.TimeStamp()));
CancelScrollBubbling(bubbling_gesture_scroll_target_.target);
}
}
touch_event_ack_queue_->Add(touch_event.unique_touch_event_id,
touch_target_.target, root_view, event_source);
blink::WebTouchEvent event(touch_event);
TransformEventTouchPositions(&event, touch_target_.transform);
touch_target_.target->ProcessTouchEvent(event, latency);
if (!active_touches_)
touch_target_.target = nullptr;
}
void RenderWidgetHostInputEventRouter::ProcessAckedTouchEvent(
const TouchEventWithLatencyInfo& event,
InputEventAckState ack_result,
RenderWidgetHostViewBase* view) {
touch_event_ack_queue_->MarkAcked(event.event.unique_touch_event_id,
ack_result, view);
// TODO(wjmaclean): Eventually we will keep track of which outgoing touch
// events are emulated and which aren't, so the decision to hand off to the
// touch emulator won't just rely on the existence of the touch emulator.
if (touch_emulator_ &&
touch_emulator_->HandleTouchEventAck(event.event, ack_result)) {
return;
}
if (!view)
return;
auto* root_view = view->GetRootView();
if (!root_view)
return;
root_view->ProcessAckedTouchEvent(event, ack_result);
}
void RenderWidgetHostInputEventRouter::RouteTouchEvent(
RenderWidgetHostViewBase* root_view,
blink::WebTouchEvent* event,
const ui::LatencyInfo& latency) {
event_targeter_->FindTargetAndDispatch(root_view, *event, latency);
}
void RenderWidgetHostInputEventRouter::SendMouseEnterOrLeaveEvents(
const 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);
// Non-root RWHVs are guaranteed to be RenderWidgetHostViewChildFrames,
// as long as they are the only embeddable RWHVs.
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);
}
// On Windows, it appears to be possible that render widget targeting could
// produce a target that is outside of the specified root. For now, we'll
// just give up in such a case. See https://crbug.com/851958.
if (cur_view != root_view)
return;
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::PointF 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(
event.PositionInWidget(), view, &transformed_point,
viz::EventSource::MOUSE)) {
transformed_point = gfx::PointF();
}
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(
event.PositionInWidget(), common_ancestor, &transformed_point,
viz::EventSource::MOUSE)) {
transformed_point = gfx::PointF();
}
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(
event.PositionInWidget(), view, &transformed_point,
viz::EventSource::MOUSE)) {
transformed_point = gfx::PointF();
}
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::ReportBubblingScrollToSameView(
const blink::WebGestureEvent& event,
const RenderWidgetHostViewBase* view) {
#if 0
// For now, we've disabled the DumpWithoutCrashing as it's no longer
// providing useful information.
// TODO(828422): Determine useful crash keys and reenable the report.
base::debug::DumpWithoutCrashing();
#endif
}
namespace {
// Given |event| in root coordinates, return an event in |target_view|'s
// coordinates.
blink::WebGestureEvent GestureEventInTarget(
const blink::WebGestureEvent& event,
RenderWidgetHostViewBase* target_view) {
const gfx::PointF point_in_target =
target_view->TransformRootPointToViewCoordSpace(event.PositionInWidget());
blink::WebGestureEvent event_for_target(event);
event_for_target.SetPositionInWidget(point_in_target);
return event_for_target;
}
} // namespace
void RenderWidgetHostInputEventRouter::BubbleScrollEvent(
RenderWidgetHostViewBase* target_view,
const blink::WebGestureEvent& event,
const RenderWidgetHostViewBase* resending_view) {
DCHECK(target_view);
DCHECK(event.GetType() == blink::WebInputEvent::kGestureScrollBegin ||
event.GetType() == blink::WebInputEvent::kGestureScrollUpdate ||
event.GetType() == blink::WebInputEvent::kGestureScrollEnd ||
event.GetType() == blink::WebInputEvent::kGestureFlingStart);
ui::LatencyInfo latency_info =
ui::WebInputEventTraits::CreateLatencyInfoForWebGestureEvent(event);
if (event.GetType() == blink::WebInputEvent::kGestureScrollBegin) {
// 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
// begin has been bubbled but the target has sent back a gesture scroll
// event ack which didn't consume any 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,
GestureEventInTarget(event, bubbling_gesture_scroll_target_.target));
} else {
first_bubbling_scroll_target_.target = target_view;
}
bubbling_gesture_scroll_target_.target = target_view;
} else { // !(event.GetType() == blink::WebInputEvent::kGestureScrollBegin)
if (!bubbling_gesture_scroll_target_.target) {
// The GestureScrollBegin event is not bubbled, don't bubble the rest of
// the scroll events.
return;
}
// Don't bubble the GSE events that are generated and sent to intermediate
// bubbling targets.
if (event.GetType() == blink::WebInputEvent::kGestureScrollEnd &&
target_view != first_bubbling_scroll_target_.target) {
return;
}
}
// If the router tries to resend a gesture scroll event back to the same
// view, we could hang.
DCHECK_NE(resending_view, bubbling_gesture_scroll_target_.target);
// We've seen reports of this, but don't know the cause yet. For now,
// instead of CHECKing or hanging, we'll report the issue and abort scroll
// bubbling.
// TODO(828422): Remove once this issue no longer occurs.
if (resending_view == bubbling_gesture_scroll_target_.target) {
ReportBubblingScrollToSameView(event, resending_view);
first_bubbling_scroll_target_.target = nullptr;
bubbling_gesture_scroll_target_.target = nullptr;
return;
}
bubbling_gesture_scroll_target_.target->ProcessGestureEvent(
GestureEventInTarget(event, bubbling_gesture_scroll_target_.target),
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;
}
}
void RenderWidgetHostInputEventRouter::SendGestureScrollBegin(
RenderWidgetHostViewBase* view,
const blink::WebGestureEvent& event) {
blink::WebGestureEvent scroll_begin(event);
scroll_begin.SetType(blink::WebInputEvent::kGestureScrollBegin);
switch (event.GetType()) {
case blink::WebInputEvent::kGestureScrollUpdate:
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;
break;
case blink::WebInputEvent::kGesturePinchBegin:
scroll_begin.data.scroll_begin.delta_x_hint = 0;
scroll_begin.data.scroll_begin.delta_y_hint = 0;
scroll_begin.data.scroll_begin.delta_hint_units =
blink::WebGestureEvent::kPrecisePixels;
break;
default:
NOTREACHED();
}
view->ProcessGestureEvent(
scroll_begin,
ui::WebInputEventTraits::CreateLatencyInfoForWebGestureEvent(event));
}
void RenderWidgetHostInputEventRouter::SendGestureScrollEnd(
RenderWidgetHostViewBase* view,
const blink::WebGestureEvent& event) {
blink::WebGestureEvent scroll_end(event);
scroll_end.SetType(blink::WebInputEvent::kGestureScrollEnd);
scroll_end.SetTimeStamp(base::TimeTicks::Now());
switch (event.GetType()) {
case blink::WebInputEvent::kGestureScrollBegin:
scroll_end.data.scroll_end.inertial_phase =
event.data.scroll_begin.inertial_phase;
scroll_end.data.scroll_end.delta_units =
event.data.scroll_begin.delta_hint_units;
break;
case blink::WebInputEvent::kGestureScrollUpdate:
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;
break;
case blink::WebInputEvent::kGesturePinchEnd:
scroll_end.data.scroll_end.inertial_phase =
blink::WebGestureEvent::kUnknownMomentumPhase;
scroll_end.data.scroll_end.delta_units =
blink::WebGestureEvent::kPrecisePixels;
break;
default:
NOTREACHED();
}
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 viz::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 viz::FrameSinkId& id) {
auto it_to_remove = owner_map_.find(id);
if (it_to_remove != owner_map_.end()) {
// If we remove a view from the observer list, we need to be sure to do a
// cleanup of the various targets and target maps, else we will end up with
// stale values if the view destructs and isn't an observer anymore.
// Note: the view the iterator points at will be deleted in the following
// call, and shouldn't be used after this point.
OnRenderWidgetHostViewBaseDestroyed(it_to_remove->second);
}
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::PointF& point,
gfx::PointF* transformed_point) {
if (!root_view)
return nullptr;
gfx::PointF point_in_screen =
point + root_view->GetViewBounds().OffsetFromOrigin();
return RenderWidgetHostImpl::From(
FindViewAtLocation(root_view, point, point_in_screen,
viz::EventSource::MOUSE, transformed_point)
.view->GetRenderWidgetHost());
}
RenderWidgetTargetResult
RenderWidgetHostInputEventRouter::FindTouchscreenGestureEventTarget(
RenderWidgetHostViewBase* root_view,
const blink::WebGestureEvent& gesture_event) {
// Since DispatchTouchscreenGestureEvent() doesn't pay any attention to the
// target we could just return nullptr for pinch events, but since we know
// where they are going we return the correct target.
if (blink::WebInputEvent::IsPinchGestureEventType(gesture_event.GetType()))
return {root_view, false, gesture_event.PositionInWidget(), true, false};
// Android sends gesture events that have no corresponding touch sequence, so
// these we hit-test explicitly.
if (gesture_event.unique_touch_event_id == 0) {
gfx::PointF transformed_point;
gfx::PointF original_point(gesture_event.PositionInWidget());
gfx::PointF original_point_in_screen(gesture_event.PositionInScreen());
return FindViewAtLocation(root_view, original_point,
original_point_in_screen, viz::EventSource::TOUCH,
&transformed_point);
}
// Remaining gesture events will defer to the gesture event target queue
// during dispatch.
return {nullptr, false, base::nullopt, true, false};
}
bool RenderWidgetHostInputEventRouter::IsViewInMap(
const RenderWidgetHostViewBase* view) const {
for (auto entry : owner_map_) {
if (entry.second == view)
return true;
}
return false;
}
void RenderWidgetHostInputEventRouter::DispatchTouchscreenGestureEvent(
RenderWidgetHostViewBase* root_view,
RenderWidgetHostViewBase* target,
const blink::WebGestureEvent& gesture_event,
const ui::LatencyInfo& latency,
const base::Optional<gfx::PointF>& target_location) {
if (gesture_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()) {
base::Optional<cc::TouchAction> target_allowed_touch_action(
cc::kTouchActionNone);
if (touchscreen_gesture_target_.target) {
target_allowed_touch_action =
(static_cast<RenderWidgetHostImpl*>(
touchscreen_gesture_target_.target->GetRenderWidgetHost()))
->input_router()
->AllowedTouchAction();
}
DCHECK(target_allowed_touch_action.has_value());
if (target_allowed_touch_action.value() &
cc::TouchAction::kTouchActionPinchZoom) {
gesture_pinch_did_send_scroll_begin_ = true;
// The pinch gesture will be sent to the root view and it may not have a
// valid touch action yet. In this case, set the touch action to auto.
rwhi->input_router()->ForceSetTouchActionAuto();
SendGestureScrollBegin(root_view, gesture_event);
} else {
// When target does not allow touch-action: pinch, instead of sending
// pinch gestures to the root frame, we send all gesture pinch events
// to the subframe target so the target can look after disposing of
// them.
in_touchscreen_gesture_pinch_ = false;
}
}
}
if (in_touchscreen_gesture_pinch_) {
root_view->ProcessGestureEvent(gesture_event, latency);
if (gesture_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, gesture_event);
}
gesture_pinch_did_send_scroll_begin_ = false;
}
return;
}
if (gesture_event.GetType() == blink::WebInputEvent::kGestureFlingCancel &&
last_fling_start_target_) {
last_fling_start_target_->ProcessGestureEvent(gesture_event, latency);
return;
}
auto gesture_target_it =
touchscreen_gesture_target_map_.find(gesture_event.unique_touch_event_id);
bool no_matching_id =
gesture_target_it == touchscreen_gesture_target_map_.end();
// 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.
bool is_gesture_start =
gesture_event.GetType() == blink::WebInputEvent::kGestureTapDown;
if (gesture_event.unique_touch_event_id == 0) {
// 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. These will use the target found by the
// RenderWidgetTargeter. These gesture events should always have a
// unique_touch_event_id of 0.
touchscreen_gesture_target_.target = target;
touchscreen_gesture_target_in_map_ = IsViewInMap(target);
if (!root_view->GetTransformToViewCoordSpace(
touchscreen_gesture_target_.target,
&touchscreen_gesture_target_.transform)) {
touchscreen_gesture_target_.transform.MakeIdentity();
if (target_location.has_value()) {
touch_target_.transform.Translate(target_location.value() -
gesture_event.PositionInWidget());
}
}
} else if (no_matching_id && is_gesture_start) {
// A long-standing Windows issues where occasionally a GestureStart is
// encountered with no targets in the event queue. We never had a repro for
// this, but perhaps we should drop these events and wait to see if a bug
// (with a repro) gets filed, then just fix it.
//
// For now, we do a synchronous-only hit test here, which even though
// incorrect is not likely to have a large effect in the short term.
UMA_HISTOGRAM_BOOLEAN("Event.FrameEventRouting.NoGestureTarget", true);
LOG(ERROR) << "Gesture sequence start detected with no target available.";
// It is still safe to continue; we will recalculate the target.
gfx::PointF transformed_point;
gfx::PointF original_point(gesture_event.PositionInWidget());
gfx::PointF original_point_in_screen(gesture_event.PositionInScreen());
auto result =
FindViewAtLocation(root_view, original_point, original_point_in_screen,
viz::EventSource::TOUCH, &transformed_point);
// Re https://crbug.com/796656): Since we are already in an error case,
// don't worry about the fact we're ignoring |result.should_query_view|, as
// this is the best we can do until we fix https://crbug.com/595422.
touchscreen_gesture_target_.target = result.view;
touchscreen_gesture_target_in_map_ = IsViewInMap(result.view);
if (!root_view->GetTransformToViewCoordSpace(
touchscreen_gesture_target_.target,
&touchscreen_gesture_target_.transform)) {
touchscreen_gesture_target_.transform.MakeIdentity();
if (target_location.has_value())
touch_target_.transform.Translate(transformed_point - original_point);
}
} else if (is_gesture_start) {
touchscreen_gesture_target_ = gesture_target_it->second;
touchscreen_gesture_target_map_.erase(gesture_target_it);
touchscreen_gesture_target_in_map_ =
IsViewInMap(touchscreen_gesture_target_.target);
// 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(gesture_event.TimeStamp()));
CancelScrollBubbling(bubbling_gesture_scroll_target_.target);
}
}
// If we set a target and it's not in the map, we won't get notified if the
// target goes away, so drop the target and the resulting events.
if (!touchscreen_gesture_target_in_map_)
touchscreen_gesture_target_.target = nullptr;
if (!touchscreen_gesture_target_.target) {
root_view->GestureEventAck(gesture_event,
INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
return;
}
blink::WebGestureEvent event(gesture_event);
gfx::PointF transformed_point(gesture_event.PositionInWidget());
touchscreen_gesture_target_.transform.TransformPoint(&transformed_point);
event.SetPositionInWidget(transformed_point);
if (events_being_flushed_) {
touchscreen_gesture_target_.target->host()
->input_router()
->ForceSetTouchActionAuto();
}
touchscreen_gesture_target_.target->ProcessGestureEvent(event, latency);
if (gesture_event.GetType() == blink::WebInputEvent::kGestureFlingStart)
last_fling_start_target_ = touchscreen_gesture_target_.target;
}
void RenderWidgetHostInputEventRouter::RouteTouchscreenGestureEvent(
RenderWidgetHostViewBase* root_view,
const blink::WebGestureEvent* event,
const ui::LatencyInfo& latency) {
DCHECK_EQ(blink::kWebGestureDeviceTouchscreen, event->SourceDevice());
event_targeter_->FindTargetAndDispatch(root_view, *event, latency);
}
RenderWidgetTargetResult
RenderWidgetHostInputEventRouter::FindTouchpadGestureEventTarget(
RenderWidgetHostViewBase* root_view,
const blink::WebGestureEvent& event) const {
if (event.GetType() != blink::WebInputEvent::kGesturePinchBegin &&
event.GetType() != blink::WebInputEvent::kGestureFlingCancel &&
event.GetType() != blink::WebInputEvent::kGestureDoubleTap) {
return {nullptr, false, base::nullopt, true, false};
}
gfx::PointF transformed_point;
return FindViewAtLocation(root_view, event.PositionInWidget(),
event.PositionInScreen(), viz::EventSource::MOUSE,
&transformed_point);
}
void RenderWidgetHostInputEventRouter::RouteTouchpadGestureEvent(
RenderWidgetHostViewBase* root_view,
const blink::WebGestureEvent* event,
const ui::LatencyInfo& latency) {
DCHECK_EQ(blink::kWebGestureDeviceTouchpad, event->SourceDevice());
event_targeter_->FindTargetAndDispatch(root_view, *event, latency);
}
void RenderWidgetHostInputEventRouter::DispatchTouchpadGestureEvent(
RenderWidgetHostViewBase* root_view,
RenderWidgetHostViewBase* target,
const blink::WebGestureEvent& touchpad_gesture_event,
const ui::LatencyInfo& latency,
const base::Optional<gfx::PointF>& target_location) {
// Touchpad gesture flings should be treated as mouse wheels for the purpose
// of routing.
if (touchpad_gesture_event.GetType() ==
blink::WebInputEvent::kGestureFlingStart) {
if (wheel_target_.target) {
blink::WebGestureEvent gesture_fling = touchpad_gesture_event;
gesture_fling.SetPositionInWidget(gesture_fling.PositionInWidget() +
wheel_target_.delta);
wheel_target_.target->ProcessGestureEvent(gesture_fling, latency);
last_fling_start_target_ = wheel_target_.target;
} else {
root_view->GestureEventAck(touchpad_gesture_event,
INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
}
return;
}
if (touchpad_gesture_event.GetType() ==
blink::WebInputEvent::kGestureFlingCancel) {
if (last_fling_start_target_) {
last_fling_start_target_->ProcessGestureEvent(touchpad_gesture_event,
latency);
} else if (target) {
target->ProcessGestureEvent(touchpad_gesture_event, latency);
} else {
root_view->GestureEventAck(touchpad_gesture_event,
INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
}
return;
}
if (target) {
touchpad_gesture_target_.target = target;
// 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.
DCHECK(target_location.has_value());
touchpad_gesture_target_.delta =
target_location.value() - touchpad_gesture_event.PositionInWidget();
// 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(touchpad_gesture_event.TimeStamp()));
CancelScrollBubbling(bubbling_gesture_scroll_target_.target);
}
}
if (!touchpad_gesture_target_.target) {
root_view->GestureEventAck(touchpad_gesture_event,
INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
return;
}
blink::WebGestureEvent gesture_event = touchpad_gesture_event;
// TODO(mohsen): Add tests to check event location.
gesture_event.SetPositionInWidget(gesture_event.PositionInWidget() +
touchpad_gesture_target_.delta);
touchpad_gesture_target_.target->ProcessGestureEvent(gesture_event, latency);
if (touchpad_gesture_event.GetType() ==
blink::WebInputEvent::kGesturePinchEnd ||
touchpad_gesture_event.GetType() ==
blink::WebInputEvent::kGestureDoubleTap) {
touchpad_gesture_target_.target = nullptr;
}
}
RenderWidgetHostViewBase*
RenderWidgetHostInputEventRouter::FindViewFromFrameSinkId(
const viz::FrameSinkId& frame_sink_id) const {
// TODO(kenrb): There should be a better way to handle hit tests to surfaces
// that are no longer valid for hit testing. See https://crbug.com/790044.
auto 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.
return iter == owner_map_.end() ? nullptr : iter->second;
}
std::vector<RenderWidgetHostView*>
RenderWidgetHostInputEventRouter::GetRenderWidgetHostViewsForTests() const {
std::vector<RenderWidgetHostView*> hosts;
for (auto entry : owner_map_)
hosts.push_back(entry.second);
return hosts;
}
RenderWidgetTargeter*
RenderWidgetHostInputEventRouter::GetRenderWidgetTargeterForTests() {
return event_targeter_.get();
}
RenderWidgetTargetResult
RenderWidgetHostInputEventRouter::FindTargetSynchronously(
RenderWidgetHostViewBase* root_view,
const blink::WebInputEvent& event) {
if (blink::WebInputEvent::IsMouseEventType(event.GetType())) {
return FindMouseEventTarget(
root_view, static_cast<const blink::WebMouseEvent&>(event));
}
if (event.GetType() == blink::WebInputEvent::kMouseWheel) {
return FindMouseWheelEventTarget(
root_view, static_cast<const blink::WebMouseWheelEvent&>(event));
}
if (blink::WebInputEvent::IsTouchEventType(event.GetType())) {
return FindTouchEventTarget(
root_view, static_cast<const blink::WebTouchEvent&>(event));
}
if (blink::WebInputEvent::IsGestureEventType(event.GetType())) {
auto gesture_event = static_cast<const blink::WebGestureEvent&>(event);
if (gesture_event.SourceDevice() ==
blink::WebGestureDevice::kWebGestureDeviceTouchscreen) {
return FindTouchscreenGestureEventTarget(root_view, gesture_event);
}
if (gesture_event.SourceDevice() ==
blink::WebGestureDevice::kWebGestureDeviceTouchpad) {
return FindTouchpadGestureEventTarget(root_view, gesture_event);
}
}
NOTREACHED();
return RenderWidgetTargetResult();
}
void RenderWidgetHostInputEventRouter::SetEventsBeingFlushed(
bool events_being_flushed) {
events_being_flushed_ = events_being_flushed;
}
void RenderWidgetHostInputEventRouter::DispatchEventToTarget(
RenderWidgetHostViewBase* root_view,
RenderWidgetHostViewBase* target,
const blink::WebInputEvent& event,
const ui::LatencyInfo& latency,
const base::Optional<gfx::PointF>& target_location) {
if (blink::WebInputEvent::IsMouseEventType(event.GetType())) {
DispatchMouseEvent(root_view, target,
static_cast<const blink::WebMouseEvent&>(event), latency,
target_location);
return;
}
if (event.GetType() == blink::WebInputEvent::kMouseWheel) {
DispatchMouseWheelEvent(
root_view, target, static_cast<const blink::WebMouseWheelEvent&>(event),
latency, target_location);
return;
}
if (blink::WebInputEvent::IsTouchEventType(event.GetType())) {
auto& touch_event = static_cast<const blink::WebTouchEvent&>(event);
TouchEventWithLatencyInfo touch_with_latency(touch_event, latency);
if (touch_emulator_ &&
touch_emulator_->HandleTouchEvent(touch_with_latency.event)) {
// We cheat a litle bit here, and assume that we know that even if the
// target is a RenderWidgetHostViewChildFrame, that it would only try to
// forward the ack to the root view anyways, so we send it there directly.
root_view->ProcessAckedTouchEvent(touch_with_latency,
INPUT_EVENT_ACK_STATE_CONSUMED);
return;
}
DispatchTouchEvent(root_view, target, touch_event, latency, target_location,
false /* not emulated */);
return;
}
if (blink::WebInputEvent::IsGestureEventType(event.GetType())) {
auto gesture_event = static_cast<const blink::WebGestureEvent&>(event);
if (gesture_event.SourceDevice() ==
blink::WebGestureDevice::kWebGestureDeviceTouchscreen) {
DispatchTouchscreenGestureEvent(root_view, target, gesture_event, latency,
target_location);
return;
}
if (gesture_event.SourceDevice() ==
blink::WebGestureDevice::kWebGestureDeviceTouchpad) {
DispatchTouchpadGestureEvent(root_view, target, gesture_event, latency,
target_location);
return;
}
}
NOTREACHED();
}
TouchEmulator* RenderWidgetHostInputEventRouter::GetTouchEmulator() {
if (!touch_emulator_)
touch_emulator_.reset(new TouchEmulator(this, last_device_scale_factor_));
return touch_emulator_.get();
}
void RenderWidgetHostInputEventRouter::ForwardEmulatedGestureEvent(
const blink::WebGestureEvent& event) {
TRACE_EVENT0("input",
"RenderWidgetHostInputEventRouter::ForwardEmulatedGestureEvent");
// It's possible that since |last_emulated_event_root_view_| was set by the
// outbound touch event that the view may have gone away. Before with dispatch
// the GestureEvent, confirm the view is still available.
if (!IsViewInMap(last_emulated_event_root_view_))
return;
DispatchTouchscreenGestureEvent(last_emulated_event_root_view_, nullptr,
event, ui::LatencyInfo(),
event.PositionInWidget());
}
void RenderWidgetHostInputEventRouter::ForwardEmulatedTouchEvent(
const blink::WebTouchEvent& event,
RenderWidgetHostViewBase* target) {
TRACE_EVENT0("input",
"RenderWidgetHostInputEventRouter::ForwardEmulatedTouchEvent");
// Here we re-use the last root view we saw for a mouse move event, or fall
// back to using |target| as the root_view if we haven't seen a mouse event;
// this latter case only happens for injected touch events.
// TODO(wjmaclean): Why doesn't this class just track its root view?
DCHECK(IsViewInMap(static_cast<RenderWidgetHostViewBase*>(target)));
last_emulated_event_root_view_ =
last_mouse_move_root_view_ ? last_mouse_move_root_view_ : target;
if (event.GetType() == blink::WebInputEvent::kTouchStart)
active_touches_ += CountChangedTouchPoints(event);
blink::WebFloatPoint position_in_widget = event.touches[0].PositionInWidget();
gfx::PointF transformed_point = target->TransformRootPointToViewCoordSpace(
gfx::PointF(position_in_widget.x, position_in_widget.y));
DispatchTouchEvent(last_emulated_event_root_view_, target, event,
ui::LatencyInfo(), transformed_point, true /* emulated */);
}
void RenderWidgetHostInputEventRouter::SetCursor(const WebCursor& cursor) {
if (!last_mouse_move_root_view_)
return;
last_device_scale_factor_ =
last_mouse_move_root_view_->current_device_scale_factor();
if (auto* cursor_manager = last_mouse_move_root_view_->GetCursorManager()) {
for (auto it : owner_map_)
cursor_manager->UpdateCursor(it.second, cursor);
}
}
void RenderWidgetHostInputEventRouter::ShowContextMenuAtPoint(
const gfx::Point& point,
const ui::MenuSourceType source_type) {
// It's possible that since |last_mouse_move_target_| was set by the
// outbound mouse event that the view may have gone away. Before dispatching
// the context menu, confirm the view is still available.
if (!IsViewInMap(last_mouse_move_target_))
return;
auto* rwhi = static_cast<RenderWidgetHostImpl*>(
last_mouse_move_target_->GetRenderWidgetHost());
DCHECK(rwhi);
rwhi->ShowContextMenuAtPoint(point, source_type);
}
void RenderWidgetHostInputEventRouter::SetMouseCaptureTarget(
RenderWidgetHostViewBase* target,
bool capture) {
if (touch_emulator_ && touch_emulator_->enabled())
return;
if (capture) {
mouse_capture_target_.target = target;
return;
}
if (mouse_capture_target_.target == target)
mouse_capture_target_.target = nullptr;
}
RenderWidgetHostImpl*
RenderWidgetHostInputEventRouter::GetMouseCaptureWidgetForTests() const {
if (mouse_capture_target_.target)
return mouse_capture_target_.target->host();
return nullptr;
}
} // namespace content