| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/renderer_host/render_widget_host_input_event_router.h" |
| |
| #include <vector> |
| |
| #include "base/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::Vector2dF& delta) { |
| for (unsigned i = 0; i < event->touches_length; ++i) { |
| event->touches[i].SetPositionInWidget( |
| event->touches[i].PositionInWidget().x + delta.x(), |
| event->touches[i].PositionInWidget().y + delta.y()); |
| } |
| } |
| |
| blink::WebGestureEvent DummyGestureScrollUpdate(base::TimeTicks time_stamp) { |
| return blink::WebGestureEvent(blink::WebInputEvent::kGestureScrollUpdate, |
| blink::WebInputEvent::kNoModifiers, time_stamp); |
| } |
| |
| 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(); |
| } |
| |
| gfx::PointF ComputePointInRootInPixels( |
| const gfx::PointF& point, |
| content::RenderWidgetHostViewBase* root_view, |
| float device_scale_factor) { |
| gfx::PointF point_in_root = point + root_view->GetOffsetFromRootSurface(); |
| return gfx::ConvertPointToPixel(device_scale_factor, point_in_root); |
| } |
| |
| } // anonymous namespace |
| |
| namespace content { |
| |
| 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; |
| } |
| |
| 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; |
| // When wheel scroll latching is disabled |
| // bubbling_gesture_scroll_target_.target should also get reset since |
| // gesture scroll events are bubbled one target at a time and they need the |
| // first target for getting bubbled to the current bubbling target. With |
| // latching enabled gesture scroll events (other than GSB) are bubbled |
| // directly to the bubbling target, the bubbling target should wait for the |
| // GSE to arrive and finish scrolling sequence rather than getting reset. |
| if (bubbling_gesture_scroll_target_.target && |
| !bubbling_gesture_scroll_target_.target |
| ->wheel_scroll_latching_enabled()) { |
| bubbling_gesture_scroll_target_.target = nullptr; |
| } |
| } |
| |
| if (view == last_mouse_move_target_) { |
| // When a child iframe is destroyed, consider its parent to be to be the |
| // most recent target, if possible. In some cases the parent might already |
| // have been destroyed, in which case the last target is cleared. |
| if (view != last_mouse_move_root_view_) |
| last_mouse_move_target_ = |
| static_cast<RenderWidgetHostViewChildFrame*>(last_mouse_move_target_) |
| ->GetParentView(); |
| else |
| last_mouse_move_target_ = nullptr; |
| |
| if (!last_mouse_move_target_ || view == last_mouse_move_root_view_) |
| last_mouse_move_root_view_ = nullptr; |
| } |
| |
| 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->primary_surface_id); |
| 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->primary_surface_id); |
| 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()), |
| 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; |
| if (root_view->IsMouseLocked()) { |
| target = root_view->host()->delegate()->GetMouseLockWidget()->GetView(); |
| } |
| |
| constexpr int mouse_button_modifiers = |
| blink::WebInputEvent::kLeftButtonDown | |
| blink::WebInputEvent::kMiddleButtonDown | |
| blink::WebInputEvent::kRightButtonDown | |
| blink::WebInputEvent::kBackButtonDown | |
| blink::WebInputEvent::kForwardButtonDown; |
| if (!target && mouse_capture_target_.target && |
| event.GetType() != blink::WebInputEvent::kMouseDown && |
| (event.GetType() == blink::WebInputEvent::kMouseUp || |
| event.GetModifiers() & mouse_button_modifiers)) { |
| 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); |
| if (result.should_query_view) { |
| return {result.view, true, transformed_point, latched_target}; |
| } |
| 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}; |
| } |
| } |
| return {target, false, transformed_point, latched_target}; |
| } |
| |
| 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}; |
| } |
| return {target, false, transformed_point, true}; |
| } |
| |
| if (root_view->wheel_scroll_latching_enabled()) { |
| 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}; |
| } |
| // For non-begin events, the target found for the previous phaseBegan is |
| // used. |
| return {nullptr, false, base::nullopt, true}; |
| } |
| |
| auto result = FindViewAtLocation(root_view, event.PositionInWidget(), |
| event.PositionInScreen(), |
| viz::EventSource::MOUSE, &transformed_point); |
| return {result.view, result.should_query_view, transformed_point, 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}; |
| } |
| |
| viz::FrameSinkId frame_sink_id; |
| bool query_renderer = false; |
| if (use_viz_hit_test_) { |
| viz::HitTestQuery* query = GetHitTestQuery(GetHostFrameSinkManager(), |
| root_view->GetRootFrameSinkId()); |
| if (!query) |
| return {root_view, false, base::nullopt, 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; |
| } |
| if (target.flags & viz::HitTestRegionFlags::kHitTestAsk) |
| query_renderer = 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}; |
| } |
| |
| 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; |
| |
| if (mouse_event.GetType() == blink::WebInputEvent::kMouseUp) |
| mouse_capture_target_.target = nullptr; |
| else if (mouse_event.GetType() == blink::WebInputEvent::kMouseDown) |
| 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() && |
| root_view->wheel_scroll_latching_enabled()) { |
| 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. Wheel scroll latching is enabled and 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); |
| |
| DCHECK(root_view->wheel_scroll_latching_enabled() || !wheel_target_.target); |
| 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, |
| 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}; |
| |
| 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) { |
| 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; |
| // TODO(wjmaclean): Instead of just computing a delta, we should extract |
| // the complete transform. We assume it doesn't change for the duration |
| // of the touch sequence, though this could be wrong; a better approach |
| // might be to always transform each point to the |touch_target_.target| |
| // for the duration of the sequence. |
| DCHECK(target_location.has_value()); |
| touch_target_.delta = |
| 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); |
| |
| if (!touch_target_.target) { |
| 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); |
| } |
| } |
| |
| blink::WebTouchEvent event(touch_event); |
| TransformEventTouchPositions(&event, touch_target_.delta); |
| touch_target_.target->ProcessTouchEvent(event, latency); |
| |
| if (!active_touches_) |
| touch_target_.target = nullptr; |
| } |
| |
| 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); |
| while (cur_view->IsRenderWidgetHostViewChildFrame()) { |
| cur_view = |
| static_cast<RenderWidgetHostViewChildFrame*>(cur_view)->GetParentView(); |
| // cur_view can possibly be nullptr for guestviews that are not currently |
| // connected to the webcontents tree. |
| if (!cur_view) { |
| last_mouse_move_target_ = target; |
| last_mouse_move_root_view_ = root_view; |
| return; |
| } |
| entered_views.push_back(cur_view); |
| } |
| // Non-root RWHVs are guaranteed to be RenderWidgetHostViewChildFrames, |
| // as long as they are the only embeddable RWHVs. |
| DCHECK_EQ(cur_view, root_view); |
| |
| cur_view = last_mouse_move_target_; |
| if (cur_view) { |
| exited_views.push_back(cur_view); |
| while (cur_view->IsRenderWidgetHostViewChildFrame()) { |
| cur_view = static_cast<RenderWidgetHostViewChildFrame*>(cur_view) |
| ->GetParentView(); |
| if (!cur_view) { |
| last_mouse_move_target_ = target; |
| last_mouse_move_root_view_ = root_view; |
| return; |
| } |
| exited_views.push_back(cur_view); |
| } |
| DCHECK_EQ(cur_view, root_view); |
| } |
| |
| // This removes common ancestors from the root downward. |
| RenderWidgetHostViewBase* common_ancestor = nullptr; |
| while (entered_views.size() > 0 && exited_views.size() > 0 && |
| entered_views.back() == exited_views.back()) { |
| common_ancestor = entered_views.back(); |
| entered_views.pop_back(); |
| exited_views.pop_back(); |
| } |
| |
| gfx::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((target_view->wheel_scroll_latching_enabled() && |
| 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 (target_view->wheel_scroll_latching_enabled()) { |
| 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; |
| } |
| |
| return; |
| } |
| |
| DCHECK(!target_view->wheel_scroll_latching_enabled()); |
| |
| // DCHECK_XNOR the current and original bubble targets. Both should be set |
| // if a bubbling gesture scroll is in progress. |
| DCHECK(!first_bubbling_scroll_target_.target == |
| !bubbling_gesture_scroll_target_.target); |
| |
| // If target_view is already set up for bubbled scrolls, we forward |
| // the event to the current scroll target without further consideration. |
| if (target_view == first_bubbling_scroll_target_.target) { |
| bubbling_gesture_scroll_target_.target->ProcessGestureEvent( |
| 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; |
| } |
| return; |
| } |
| |
| // Disregard GestureScrollEnd events going to non-current targets. |
| // These should only happen on ACKs of synthesized GSE events that are |
| // sent from SendGestureScrollEnd calls, and are not relevant here. |
| if (event.GetType() == blink::WebInputEvent::kGestureScrollEnd) |
| return; |
| |
| // This is a special case to catch races where multiple GestureScrollUpdates |
| // have been sent to a renderer before the first one was ACKed, and the ACK |
| // caused a bubble retarget. In this case they all get forwarded. |
| if (target_view == bubbling_gesture_scroll_target_.target) { |
| bubbling_gesture_scroll_target_.target->ProcessGestureEvent( |
| GestureEventInTarget(event, bubbling_gesture_scroll_target_.target), |
| latency_info); |
| return; |
| } |
| |
| // If target_view has unrelated gesture events in progress, do |
| // not proceed. This could cause confusion between independent |
| // scrolls. |
| if (target_view == touchscreen_gesture_target_.target || |
| target_view == touchpad_gesture_target_.target || |
| target_view == touch_target_.target) |
| return; |
| |
| // This accounts for bubbling through nested OOPIFs. A gesture scroll has |
| // been bubbled but the target has sent back a gesture scroll event ack with |
| // unused scroll delta, and so another level of bubbling is needed. This |
| // requires a GestureScrollEnd be sent to the last view, which will no |
| // longer be the scroll target. |
| if (bubbling_gesture_scroll_target_.target) { |
| SendGestureScrollEnd( |
| bubbling_gesture_scroll_target_.target, |
| GestureEventInTarget(event, bubbling_gesture_scroll_target_.target)); |
| } else { |
| first_bubbling_scroll_target_.target = target_view; |
| } |
| |
| bubbling_gesture_scroll_target_.target = target_view; |
| |
| SendGestureScrollBegin(target_view, GestureEventInTarget(event, target_view)); |
| target_view->ProcessGestureEvent(GestureEventInTarget(event, target_view), |
| latency_info); |
| } |
| |
| 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: |
| DCHECK(view->wheel_scroll_latching_enabled()); |
| 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}; |
| |
| // 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}; |
| } |
| |
| 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) { |
| // Temporary logging for https://crbug.com/824774. |
| static auto* target_source_key = base::debug::AllocateCrashKeyString( |
| "touchscreen-gesture-target-source", base::debug::CrashKeySize::Size32); |
| base::debug::SetCrashKeyString(target_source_key, "input"); |
| |
| 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; |
| if (touchscreen_gesture_target_.target) { |
| target_allowed_touch_action = |
| (static_cast<RenderWidgetHostImpl*>( |
| touchscreen_gesture_target_.target->GetRenderWidgetHost())) |
| ->input_router() |
| ->AllowedTouchAction(); |
| } |
| if (target_allowed_touch_action.value() & |
| cc::TouchAction::kTouchActionPinchZoom) { |
| gesture_pinch_did_send_scroll_begin_ = true; |
| 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; |
| } |
| |
| 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); |
| base::debug::SetCrashKeyString(target_source_key, "touch_id=0"); |
| touchscreen_gesture_target_.delta = |
| target_location.has_value() |
| ? target_location.value() - gesture_event.PositionInWidget() |
| : gfx::Vector2dF(); |
| } 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); |
| base::debug::SetCrashKeyString(target_source_key, "no_matching_id"); |
| touchscreen_gesture_target_.delta = 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); |
| event.SetPositionInWidget(event.PositionInWidget() + |
| touchscreen_gesture_target_.delta); |
| // Temporary logging for https://crbug.com/824774. |
| static auto* target_ptr_key = base::debug::AllocateCrashKeyString( |
| "touchscreen-gesture-target-ptr", base::debug::CrashKeySize::Size64); |
| base::debug::SetCrashKeyString( |
| target_ptr_key, |
| base::StringPrintf("%p", touchscreen_gesture_target_.target)); |
| static auto* root_ptr_key = base::debug::AllocateCrashKeyString( |
| "touchscreen-gesture-root-ptr", base::debug::CrashKeySize::Size64); |
| base::debug::SetCrashKeyString(root_ptr_key, |
| base::StringPrintf("%p", root_view)); |
| static auto* target_ptr_in_map_key = base::debug::AllocateCrashKeyString( |
| "touchscreen-gesture-target-in-map", base::debug::CrashKeySize::Size32); |
| base::debug::SetCrashKeyString( |
| target_ptr_in_map_key, |
| touchscreen_gesture_target_in_map_ ? "true" : "false"); |
| static auto* map_size_key = base::debug::AllocateCrashKeyString( |
| "touchscreen-gesture-map-size", base::debug::CrashKeySize::Size32); |
| base::debug::SetCrashKeyString( |
| map_size_key, |
| base::StringPrintf("%u", static_cast<int>(owner_map_.size()))); |
| |
| touchscreen_gesture_target_.target->ProcessGestureEvent(event, latency); |
| } |
| |
| void RenderWidgetHostInputEventRouter::RouteTouchscreenGestureEvent( |
| RenderWidgetHostViewBase* root_view, |
| 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::kGestureFlingStart) { |
| return {nullptr, false, base::nullopt, true}; |
| } |
| |
| gfx::PointF transformed_point; |
| return FindViewAtLocation(root_view, event.PositionInWidget(), |
| event.PositionInScreen(), viz::EventSource::TOUCH, |
| &transformed_point); |
| } |
| |
| void RenderWidgetHostInputEventRouter::RouteTouchpadGestureEvent( |
| RenderWidgetHostViewBase* root_view, |
| 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) { |
| 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); |
| } |
| |
| 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::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); |
| 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"); |
| DCHECK(last_emulated_event_root_view_); |
| 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); |
| } |
| |
| 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) { |
| DCHECK(last_mouse_move_target_); |
| |
| auto* rwhi = static_cast<RenderWidgetHostImpl*>( |
| last_mouse_move_target_->GetRenderWidgetHost()); |
| DCHECK(rwhi); |
| rwhi->ShowContextMenuAtPoint(point, source_type); |
| } |
| |
| } // namespace content |