blob: 79e3fb5dfb400e37417fe1f7ed8fc162170d1a8b [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/stl_util.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 "content/public/browser/render_widget_host_iterator.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);
}
}
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 {
TouchEventWithLatencyInfo touch_event;
RenderWidgetHostViewBase* target_view;
RenderWidgetHostViewBase* root_view;
TouchEventSource touch_event_source;
TouchEventAckStatus touch_event_ack_status;
InputEventAckState ack_result;
};
explicit TouchEventAckQueue(RenderWidgetHostInputEventRouter* client)
: client_(client) {
DCHECK(client_);
}
void Add(const TouchEventWithLatencyInfo& touch_event,
RenderWidgetHostViewBase* target_view,
RenderWidgetHostViewBase* root_view,
TouchEventSource touch_event_source,
TouchEventAckStatus touch_event_ack_status,
InputEventAckState ack_result);
void Add(const TouchEventWithLatencyInfo& touch_event,
RenderWidgetHostViewBase* target_view,
RenderWidgetHostViewBase* root_view,
TouchEventSource touch_event_source);
void MarkAcked(const TouchEventWithLatencyInfo& touch_event,
InputEventAckState ack_result,
RenderWidgetHostViewBase* target_view);
void UpdateQueueAfterTargetDestroyed(RenderWidgetHostViewBase* target_view);
size_t length_for_testing() { return ack_queue_.size(); }
private:
void ProcessAckedTouchEvents();
void ReportTouchEventAckQueueUmaStats();
std::deque<AckData> ack_queue_;
RenderWidgetHostInputEventRouter* client_;
};
void TouchEventAckQueue::Add(const TouchEventWithLatencyInfo& touch_event,
RenderWidgetHostViewBase* target_view,
RenderWidgetHostViewBase* root_view,
TouchEventSource touch_event_source,
TouchEventAckStatus touch_event_ack_status,
InputEventAckState ack_result) {
AckData data = {touch_event,
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(const TouchEventWithLatencyInfo& touch_event,
RenderWidgetHostViewBase* target_view,
RenderWidgetHostViewBase* root_view,
TouchEventSource touch_event_source) {
Add(touch_event, target_view, root_view, touch_event_source,
TouchEventAckStatus::TouchEventNotAcked, INPUT_EVENT_ACK_STATE_UNKNOWN);
}
void TouchEventAckQueue::MarkAcked(const TouchEventWithLatencyInfo& touch_event,
InputEventAckState ack_result,
RenderWidgetHostViewBase* target_view) {
auto it = find_if(ack_queue_.begin(), ack_queue_.end(),
[touch_event](AckData data) {
return data.touch_event.event.unique_touch_event_id ==
touch_event.event.unique_touch_event_id;
});
if (it == ack_queue_.end()) {
// If the touch-event was sent directly to the view without going through
// RenderWidgetHostInputEventRouter, as is the case with AndroidWebView,
// then we must ack it directly.
DCHECK(target_view);
target_view->ProcessAckedTouchEvent(touch_event, ack_result);
return;
}
DCHECK(it->touch_event_ack_status != TouchEventAckStatus::TouchEventAcked);
DCHECK(target_view && target_view == it->target_view);
it->touch_event = touch_event;
it->touch_event_ack_status = TouchEventAckStatus::TouchEventAcked;
it->ack_result = ack_result;
ProcessAckedTouchEvents();
}
void TouchEventAckQueue::ProcessAckedTouchEvents() {
if (ack_queue_.empty())
return;
TouchEmulator* touch_emulator =
client_->has_touch_emulator() ? client_->GetTouchEmulator() : nullptr;
while (!ack_queue_.empty() && ack_queue_.front().touch_event_ack_status ==
TouchEventAckStatus::TouchEventAcked) {
TouchEventAckQueue::AckData ack_data = ack_queue_.front();
ack_queue_.pop_front();
if ((!touch_emulator ||
!touch_emulator->HandleTouchEventAck(ack_data.touch_event.event,
ack_data.ack_result)) &&
(client_->IsViewInMap(ack_data.root_view) ||
client_->ViewMapIsEmpty())) {
// Forward acked event and result to the root view associated with the
// event. The view map is only empty for AndroidWebView.
ack_data.root_view->ProcessAckedTouchEvent(ack_data.touch_event,
ack_data.ack_result);
}
}
}
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.
base::EraseIf(ack_queue_, [target_view](AckData data) {
return data.root_view == target_view;
});
// 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();
}
RenderWidgetHostInputEventRouter::TouchscreenPinchState::TouchscreenPinchState()
: state_(PinchState::NONE) {}
bool RenderWidgetHostInputEventRouter::TouchscreenPinchState::IsInPinch()
const {
switch (state_) {
case PinchState::NONE:
case PinchState::EXISTING_BUBBLING_TO_ROOT:
return false;
case PinchState::PINCH_WITH_ROOT_GESTURE_TARGET:
case PinchState::PINCH_WHILE_BUBBLING_TO_ROOT:
case PinchState::PINCH_DURING_CHILD_GESTURE:
return true;
}
}
bool RenderWidgetHostInputEventRouter::TouchscreenPinchState::
NeedsWrappingScrollSequence() const {
switch (state_) {
case PinchState::NONE:
case PinchState::PINCH_DURING_CHILD_GESTURE:
return true;
case PinchState::EXISTING_BUBBLING_TO_ROOT:
case PinchState::PINCH_WITH_ROOT_GESTURE_TARGET:
case PinchState::PINCH_WHILE_BUBBLING_TO_ROOT:
return false;
}
}
void RenderWidgetHostInputEventRouter::TouchscreenPinchState::
DidStartBubblingToRoot() {
switch (state_) {
case PinchState::NONE:
state_ = PinchState::EXISTING_BUBBLING_TO_ROOT;
break;
case PinchState::PINCH_DURING_CHILD_GESTURE:
state_ = PinchState::PINCH_WHILE_BUBBLING_TO_ROOT;
break;
case PinchState::EXISTING_BUBBLING_TO_ROOT:
case PinchState::PINCH_WITH_ROOT_GESTURE_TARGET:
case PinchState::PINCH_WHILE_BUBBLING_TO_ROOT:
NOTREACHED();
}
}
void RenderWidgetHostInputEventRouter::TouchscreenPinchState::
DidStopBubblingToRoot() {
DCHECK_EQ(PinchState::EXISTING_BUBBLING_TO_ROOT, state_);
state_ = PinchState::NONE;
}
void RenderWidgetHostInputEventRouter::TouchscreenPinchState::
DidStartPinchInRoot() {
DCHECK_EQ(PinchState::NONE, state_);
state_ = PinchState::PINCH_WITH_ROOT_GESTURE_TARGET;
}
void RenderWidgetHostInputEventRouter::TouchscreenPinchState::
DidStartPinchInChild() {
switch (state_) {
case PinchState::NONE:
state_ = PinchState::PINCH_DURING_CHILD_GESTURE;
break;
case PinchState::EXISTING_BUBBLING_TO_ROOT:
state_ = PinchState::PINCH_WHILE_BUBBLING_TO_ROOT;
break;
case PinchState::PINCH_WITH_ROOT_GESTURE_TARGET:
case PinchState::PINCH_WHILE_BUBBLING_TO_ROOT:
case PinchState::PINCH_DURING_CHILD_GESTURE:
NOTREACHED();
}
}
void RenderWidgetHostInputEventRouter::TouchscreenPinchState::DidStopPinch() {
switch (state_) {
case PinchState::PINCH_WITH_ROOT_GESTURE_TARGET:
state_ = PinchState::NONE;
break;
case PinchState::PINCH_WHILE_BUBBLING_TO_ROOT:
state_ = PinchState::EXISTING_BUBBLING_TO_ROOT;
break;
case PinchState::PINCH_DURING_CHILD_GESTURE:
state_ = PinchState::NONE;
break;
case PinchState::NONE:
case PinchState::EXISTING_BUBBLING_TO_ROOT:
NOTREACHED();
}
}
bool RenderWidgetHostInputEventRouter::HasEventsPendingDispatch() const {
return event_targeter_->HasEventsPendingDispatch();
}
size_t RenderWidgetHostInputEventRouter::TouchEventAckQueueLengthForTesting()
const {
return touch_event_ack_queue_->length_for_testing();
}
size_t RenderWidgetHostInputEventRouter::RegisteredViewCountForTesting() const {
return owner_map_.size();
}
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_) {
touch_target_ = nullptr;
active_touches_ = 0;
}
touch_event_ack_queue_->UpdateQueueAfterTargetDestroyed(view);
if (view == wheel_target_)
wheel_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 == view)
it.second = nullptr;
}
if (view == mouse_capture_target_)
mouse_capture_target_ = nullptr;
if (view == touchscreen_gesture_target_)
touchscreen_gesture_target_ = nullptr;
if (view == touchpad_gesture_target_)
touchpad_gesture_target_ = nullptr;
if (view == bubbling_gesture_scroll_target_) {
bubbling_gesture_scroll_target_ = nullptr;
bubbling_gesture_scroll_origin_ = nullptr;
} else if (view == bubbling_gesture_scroll_origin_) {
bubbling_gesture_scroll_origin_ = 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),
event_targeter_(std::make_unique<RenderWidgetTargeter>(this)),
use_viz_hit_test_(features::IsVizHitTestingEnabled()),
touch_event_ack_queue_(new TouchEventAckQueue(this)),
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_ &&
(event.GetType() == blink::WebInputEvent::kMouseUp ||
IsMouseButtonDown(event))) {
target = mouse_capture_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)) {
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)) {
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};
}
// TODO(riajiang): Get rid of |point_in_screen| since it's not used.
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) {
*transformed_point = point;
return {root_view, false, *transformed_point, false, false};
}
float device_scale_factor = root_view->GetDeviceScaleFactor();
DCHECK_GT(device_scale_factor, 0.0f);
gfx::PointF point_in_pixels =
gfx::ConvertPointToPixel(device_scale_factor, point);
viz::Target target = query->FindTargetForLocationStartingFrom(
source, point_in_pixels, root_view->GetFrameSinkId());
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_ = 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;
}
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) {
if (!root_view->IsMouseLocked()) {
if (mouse_wheel_event.phase == blink::WebMouseWheelEvent::kPhaseBegan) {
wheel_target_ = target;
} else {
if (wheel_target_) {
DCHECK(!target);
target = wheel_target_;
} else if ((mouse_wheel_event.phase ==
blink::WebMouseWheelEvent::kPhaseEnded ||
mouse_wheel_event.momentum_phase ==
blink::WebMouseWheelEvent::kPhaseEnded) &&
bubbling_gesture_scroll_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.
CancelScrollBubbling();
}
}
}
if (!target) {
root_view->WheelEventAck(mouse_wheel_event,
INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
return;
}
blink::WebMouseWheelEvent event = mouse_wheel_event;
gfx::PointF point_in_target;
if (target_location) {
point_in_target = target_location.value();
} else {
point_in_target = target->TransformRootPointToViewCoordSpace(
mouse_wheel_event.PositionInWidget());
}
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_ = 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;
if (is_sequence_start) {
touch_target_ = target;
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_ && !IsViewInMap(touch_target_)) {
NOTREACHED() << "Touch events should not be routed to a destroyed target "
"View.";
touch_target_ = nullptr;
base::debug::DumpWithoutCrashing();
}
TouchEventAckQueue::TouchEventSource event_source =
is_emulated_touchevent
? TouchEventAckQueue::TouchEventSource::EmulatedTouchEvent
: TouchEventAckQueue::TouchEventSource::SystemTouchEvent;
if (!touch_target_) {
touch_event_ack_queue_->Add(
TouchEventWithLatencyInfo(touch_event), nullptr, root_view,
event_source, TouchEventAckQueue::TouchEventAckStatus::TouchEventAcked,
INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
return;
}
gfx::Transform transform;
if (!root_view->GetTransformToViewCoordSpace(touch_target_, &transform)) {
// Fall-back to just using the delta if we are unable to get the full
// transform.
transform.MakeIdentity();
if (target_location.has_value()) {
transform.Translate(target_location.value() -
touch_event.touches[0].PositionInWidget());
} else {
// GetTransformToViewCoordSpace() fails when viz_hit_test is off but
// TransformRootPointToViewCoordSpace() still works at this case.
// TODO(crbug.com/917015) remove the extra code when viz_hit_test is
// always on.
gfx::PointF point_in_target =
touch_target_->TransformRootPointToViewCoordSpace(
touch_event.touches[0].PositionInWidget());
transform.Translate(point_in_target -
touch_event.touches[0].PositionInWidget());
}
}
if (is_sequence_start) {
CancelScrollBubblingIfConflicting(touch_target_);
}
touch_event_ack_queue_->Add(TouchEventWithLatencyInfo(touch_event),
touch_target_, root_view, event_source);
blink::WebTouchEvent event(touch_event);
TransformEventTouchPositions(&event, transform);
touch_target_->ProcessTouchEvent(event, latency);
if (!active_touches_)
touch_target_ = nullptr;
}
void RenderWidgetHostInputEventRouter::ProcessAckedTouchEvent(
const TouchEventWithLatencyInfo& event,
InputEventAckState ack_result,
RenderWidgetHostViewBase* view) {
touch_event_ack_queue_->MarkAcked(event, ack_result, view);
}
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)) {
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)) {
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)) {
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 {
// Returns true if |target_view| is one of |starting_view|'s ancestors.
// If |stay_within| is provided, we only consider ancestors within that
// sub-tree.
bool IsAncestorView(RenderWidgetHostViewChildFrame* starting_view,
const RenderWidgetHostViewBase* target_view,
const RenderWidgetHostViewBase* stay_within = nullptr) {
RenderWidgetHostViewBase* cur_view = starting_view->GetParentView();
while (cur_view) {
if (cur_view == target_view)
return true;
if (stay_within && cur_view == stay_within)
return false;
cur_view = cur_view->IsRenderWidgetHostViewChildFrame()
? static_cast<RenderWidgetHostViewChildFrame*>(cur_view)
->GetParentView()
: nullptr;
}
return false;
}
// 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
bool RenderWidgetHostInputEventRouter::BubbleScrollEvent(
RenderWidgetHostViewBase* target_view,
RenderWidgetHostViewChildFrame* resending_view,
const blink::WebGestureEvent& event) {
DCHECK(target_view);
DCHECK(event.GetType() == blink::WebInputEvent::kGestureScrollBegin ||
event.GetType() == blink::WebInputEvent::kGestureScrollUpdate ||
event.GetType() == blink::WebInputEvent::kGestureScrollEnd);
ui::LatencyInfo latency_info =
ui::WebInputEventTraits::CreateLatencyInfoForWebGestureEvent(event);
if (event.GetType() == blink::WebInputEvent::kGestureScrollBegin) {
forced_last_fling_start_target_to_stop_flinging_for_test_ = false;
// 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_view == touchpad_gesture_target_ ||
target_view == touch_target_) {
return false;
}
// A view is trying to bubble a separate scroll sequence while we have
// ongoing bubbling.
if (bubbling_gesture_scroll_target_ &&
bubbling_gesture_scroll_target_ != resending_view) {
return false;
}
// 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_) {
SendGestureScrollEnd(
bubbling_gesture_scroll_target_,
GestureEventInTarget(event, bubbling_gesture_scroll_target_));
} else {
bubbling_gesture_scroll_origin_ = resending_view;
}
bubbling_gesture_scroll_target_ = target_view;
bubbling_gesture_scroll_source_device_ = event.SourceDevice();
DCHECK(IsAncestorView(bubbling_gesture_scroll_origin_,
bubbling_gesture_scroll_target_));
} else { // !(event.GetType() == blink::WebInputEvent::kGestureScrollBegin)
if (!bubbling_gesture_scroll_target_) {
// Drop any acked events that come in after bubbling has ended.
// TODO(mcnee): If we inform |bubbling_gesture_scroll_origin_| and the
// intermediate views of the end of bubbling, we could presumably DCHECK
// that we have a target.
return false;
}
// Don't bubble the GSE events that are generated and sent to intermediate
// bubbling targets.
if (event.GetType() == blink::WebInputEvent::kGestureScrollEnd &&
resending_view != bubbling_gesture_scroll_origin_) {
return true;
}
}
// 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_);
// 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_) {
ReportBubblingScrollToSameView(event, resending_view);
CancelScrollBubbling();
return false;
}
const bool touchscreen_bubble_to_root =
event.SourceDevice() == blink::kWebGestureDeviceTouchscreen &&
!bubbling_gesture_scroll_target_->IsRenderWidgetHostViewChildFrame();
if (touchscreen_bubble_to_root) {
if (event.GetType() == blink::WebInputEvent::kGestureScrollBegin) {
touchscreen_pinch_state_.DidStartBubblingToRoot();
// If a pinch's scroll sequence is sent to an OOPIF and the pinch
// begin event is dispatched to the root before the scroll begin is
// bubbled to the root, a wrapping scroll begin will have already been
// sent to the root.
if (touchscreen_pinch_state_.IsInPinch()) {
return true;
}
} else if (event.GetType() == blink::WebInputEvent::kGestureScrollEnd) {
touchscreen_pinch_state_.DidStopBubblingToRoot();
}
}
bubbling_gesture_scroll_target_->ProcessGestureEvent(
GestureEventInTarget(event, bubbling_gesture_scroll_target_),
latency_info);
if (event.GetType() == blink::WebInputEvent::kGestureScrollEnd) {
bubbling_gesture_scroll_origin_ = nullptr;
bubbling_gesture_scroll_target_ = nullptr;
bubbling_gesture_scroll_source_device_ =
blink::kWebGestureDeviceUninitialized;
}
return true;
}
void RenderWidgetHostInputEventRouter::SendGestureScrollBegin(
RenderWidgetHostViewBase* view,
const blink::WebGestureEvent& event) {
DCHECK_EQ(blink::WebInputEvent::kGesturePinchBegin, event.GetType());
DCHECK_EQ(blink::kWebGestureDeviceTouchscreen, event.SourceDevice());
blink::WebGestureEvent scroll_begin(event);
scroll_begin.SetType(blink::WebInputEvent::kGestureScrollBegin);
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;
view->ProcessGestureEvent(
scroll_begin,
ui::WebInputEventTraits::CreateLatencyInfoForWebGestureEvent(
scroll_begin));
}
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::kGesturePinchEnd:
DCHECK_EQ(blink::kWebGestureDeviceTouchscreen, event.SourceDevice());
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(scroll_end));
}
void RenderWidgetHostInputEventRouter::SendGestureScrollEnd(
RenderWidgetHostViewBase* view,
blink::WebGestureDevice source_device) {
blink::WebGestureEvent scroll_end(blink::WebInputEvent::kGestureScrollEnd,
blink::WebInputEvent::kNoModifiers,
base::TimeTicks::Now(), source_device);
scroll_end.data.scroll_end.inertial_phase =
blink::WebGestureEvent::kUnknownMomentumPhase;
scroll_end.data.scroll_end.delta_units =
blink::WebGestureEvent::kPrecisePixels;
view->ProcessGestureEvent(
scroll_end,
ui::WebInputEventTraits::CreateLatencyInfoForWebGestureEvent(scroll_end));
}
void RenderWidgetHostInputEventRouter::WillDetachChildView(
const RenderWidgetHostViewChildFrame* detaching_view) {
// If necessary, cancel ongoing scroll bubbling in response to a frame
// connector change.
if (!bubbling_gesture_scroll_target_ || !bubbling_gesture_scroll_origin_)
return;
// We cancel bubbling only when the child view affects the current scroll
// bubbling sequence.
if (detaching_view == bubbling_gesture_scroll_origin_ ||
IsAncestorView(bubbling_gesture_scroll_origin_, detaching_view)) {
CancelScrollBubbling();
}
}
void RenderWidgetHostInputEventRouter::CancelScrollBubbling() {
DCHECK(bubbling_gesture_scroll_target_);
SendGestureScrollEnd(bubbling_gesture_scroll_target_,
bubbling_gesture_scroll_source_device_);
const bool touchscreen_bubble_to_root =
bubbling_gesture_scroll_source_device_ ==
blink::kWebGestureDeviceTouchscreen &&
!bubbling_gesture_scroll_target_->IsRenderWidgetHostViewChildFrame();
if (touchscreen_bubble_to_root)
touchscreen_pinch_state_.DidStopBubblingToRoot();
// TODO(mcnee): We should also inform |bubbling_gesture_scroll_origin_| that
// we are no longer bubbling its events, otherwise it could continue to send
// them and interfere with a new scroll gesture being bubbled.
// See https://crbug.com/828422
bubbling_gesture_scroll_origin_ = nullptr;
bubbling_gesture_scroll_target_ = nullptr;
bubbling_gesture_scroll_source_device_ =
blink::kWebGestureDeviceUninitialized;
}
void RenderWidgetHostInputEventRouter::CancelScrollBubblingIfConflicting(
const RenderWidgetHostViewBase* target) {
if (!target)
return;
if (!bubbling_gesture_scroll_target_ || !bubbling_gesture_scroll_origin_)
return;
if (IsAncestorView(bubbling_gesture_scroll_origin_, target,
bubbling_gesture_scroll_target_)) {
CancelScrollBubbling();
}
}
void RenderWidgetHostInputEventRouter::StopFling() {
if (!bubbling_gesture_scroll_target_)
return;
if (!last_fling_start_target_ || !last_fling_start_target_->host())
return;
// The last_fling_start_target_'s fling controller must stop flinging when its
// generated GSUs are not consumed by the bubbling target view.
last_fling_start_target_->host()->StopFling();
forced_last_fling_start_target_to_stop_flinging_for_test_ = true;
}
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 {
DCHECK(!is_registered(view->GetFrameSinkId()) ||
owner_map_.find(view->GetFrameSinkId())->second == view);
return is_registered(view->GetFrameSinkId());
}
bool RenderWidgetHostInputEventRouter::ViewMapIsEmpty() const {
return owner_map_.empty();
}
namespace {
bool IsPinchCurrentlyAllowedInTarget(RenderWidgetHostViewBase* target) {
base::Optional<cc::TouchAction> target_allowed_touch_action(
cc::kTouchActionNone);
if (target) {
target_allowed_touch_action =
(static_cast<RenderWidgetHostImpl*>(target->GetRenderWidgetHost()))
->input_router()
->AllowedTouchAction();
}
DCHECK(target_allowed_touch_action.has_value());
return (target_allowed_touch_action.value() &
cc::TouchAction::kTouchActionPinchZoom);
}
} // namespace
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) {
if (root_view == touchscreen_gesture_target_) {
// If the root view is the current gesture target, there is no need to
// wrap the pinch events ourselves.
touchscreen_pinch_state_.DidStartPinchInRoot();
} else if (IsPinchCurrentlyAllowedInTarget(touchscreen_gesture_target_)) {
// If pinch is not allowed in the child, don't do any diverting of the
// pinch events to the root. Have the events go to the child whose
// TouchActionFilter will discard them.
auto* root_rwhi =
static_cast<RenderWidgetHostImpl*>(root_view->GetRenderWidgetHost());
// 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.
root_rwhi->input_router()->ForceSetTouchActionAuto();
if (touchscreen_pinch_state_.NeedsWrappingScrollSequence()) {
// If the root view is not the gesture target, and a scroll gesture has
// not already started in the root from scroll bubbling, then we need
// to warp the diverted pinch events in a GestureScrollBegin/End.
DCHECK(!root_rwhi->is_in_touchscreen_gesture_scroll());
SendGestureScrollBegin(root_view, gesture_event);
}
touchscreen_pinch_state_.DidStartPinchInChild();
}
}
if (touchscreen_pinch_state_.IsInPinch()) {
root_view->ProcessGestureEvent(gesture_event, latency);
if (gesture_event.GetType() == blink::WebInputEvent::kGesturePinchEnd) {
const bool send_scroll_end =
touchscreen_pinch_state_.NeedsWrappingScrollSequence();
touchscreen_pinch_state_.DidStopPinch();
if (send_scroll_end) {
auto* root_rwhi = static_cast<RenderWidgetHostImpl*>(
root_view->GetRenderWidgetHost());
DCHECK(root_rwhi->is_in_touchscreen_gesture_scroll());
SendGestureScrollEnd(root_view, gesture_event);
}
}
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.
const bool is_gesture_start =
gesture_event.GetType() == blink::WebInputEvent::kGestureTapDown;
base::Optional<gfx::PointF> fallback_target_location;
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. They must have a non-null target in order
// to get the coordinate transform.
DCHECK(target);
touchscreen_gesture_target_ = target;
touchscreen_gesture_target_in_map_ = IsViewInMap(target);
fallback_target_location = target_location;
} 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_ = result.view;
touchscreen_gesture_target_in_map_ = IsViewInMap(result.view);
fallback_target_location = transformed_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_);
// Abort any scroll bubbling in progress to avoid double entry.
CancelScrollBubblingIfConflicting(touchscreen_gesture_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_ = nullptr;
if (!touchscreen_gesture_target_) {
root_view->GestureEventAck(gesture_event,
INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
return;
}
blink::WebGestureEvent event(gesture_event);
gfx::PointF point_in_target;
// This |fallback_target_location| is fast path when
// |gesture_event.unique_touch_event_id == 0| or
// |no_matching_id && is_gesture_start|, we did actually do hit testing in
// these two cases. |target_location| pass in maybe wrong in other cases.
if (fallback_target_location) {
point_in_target = fallback_target_location.value();
} else {
point_in_target =
touchscreen_gesture_target_->TransformRootPointToViewCoordSpace(
gesture_event.PositionInWidget());
}
event.SetPositionInWidget(point_in_target);
if (events_being_flushed_) {
touchscreen_gesture_target_->host()
->input_router()
->ForceSetTouchActionAuto();
}
touchscreen_gesture_target_->ProcessGestureEvent(event, latency);
if (gesture_event.GetType() == blink::WebInputEvent::kGestureFlingStart)
last_fling_start_target_ = touchscreen_gesture_target_;
// If we have one of the following events, then the user has lifted their
// last finger.
const bool is_gesture_end =
gesture_event.GetType() == blink::WebInputEvent::kGestureTap ||
gesture_event.GetType() == blink::WebInputEvent::kGestureLongTap ||
gesture_event.GetType() == blink::WebInputEvent::kGestureDoubleTap ||
gesture_event.GetType() == blink::WebInputEvent::kGestureTwoFingerTap ||
gesture_event.GetType() == blink::WebInputEvent::kGestureScrollEnd ||
gesture_event.GetType() == blink::WebInputEvent::kGestureFlingStart;
if (is_gesture_end)
touchscreen_gesture_target_ = nullptr;
}
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_) {
blink::WebGestureEvent gesture_fling = touchpad_gesture_event;
gfx::PointF point_in_target =
wheel_target_->TransformRootPointToViewCoordSpace(
gesture_fling.PositionInWidget());
gesture_fling.SetPositionInWidget(point_in_target);
wheel_target_->ProcessGestureEvent(gesture_fling, latency);
last_fling_start_target_ = wheel_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;
// Abort any scroll bubbling in progress to avoid double entry.
CancelScrollBubblingIfConflicting(touchpad_gesture_target_);
}
if (!touchpad_gesture_target_) {
root_view->GestureEventAck(touchpad_gesture_event,
INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
return;
}
blink::WebGestureEvent gesture_event = touchpad_gesture_event;
gfx::PointF point_in_target;
if (target_location) {
point_in_target = target_location.value();
} else {
point_in_target =
touchpad_gesture_target_->TransformRootPointToViewCoordSpace(
gesture_event.PositionInWidget());
}
gesture_event.SetPositionInWidget(point_in_target);
touchpad_gesture_target_->ProcessGestureEvent(gesture_event, latency);
if (touchpad_gesture_event.GetType() ==
blink::WebInputEvent::kGesturePinchEnd ||
touchpad_gesture_event.GetType() ==
blink::WebInputEvent::kGestureDoubleTap) {
touchpad_gesture_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;
}
bool RenderWidgetHostInputEventRouter::ShouldContinueHitTesting(
RenderWidgetHostViewBase* target_view) const {
// TODO(kenrb, riajiang): It would be better if we could determine if the
// event's point has a chance of hitting an embedded child and returning
// false if not, but Viz hit testing does not easily support that. This
// currently assumes any embedded view could potentially be the event
// target.
if (!use_viz_hit_test_)
return true;
// Determine if |view| has any embedded children that could potentially
// receive the event.
auto* widget_host =
static_cast<RenderWidgetHostImpl*>(target_view->GetRenderWidgetHost());
std::unique_ptr<RenderWidgetHostIterator> child_widgets(
widget_host->GetEmbeddedRenderWidgetHosts());
if (child_widgets->GetNextHost())
return true;
return false;
}
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;
return;
}
if (mouse_capture_target_ == target)
mouse_capture_target_ = nullptr;
}
RenderWidgetHostImpl*
RenderWidgetHostInputEventRouter::GetMouseCaptureWidgetForTests() const {
if (mouse_capture_target_)
return mouse_capture_target_->host();
return nullptr;
}
} // namespace content