| // Copyright 2017 The Chromium Authors |
| // 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/input/input_router_impl.h" |
| |
| #include <math.h> |
| |
| #include <utility> |
| |
| #include "base/auto_reset.h" |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "content/browser/renderer_host/input/gesture_event_queue.h" |
| #include "content/browser/renderer_host/input/input_disposition_handler.h" |
| #include "content/browser/renderer_host/input/input_router_client.h" |
| #include "content/common/content_constants_internal.h" |
| #include "content/common/input/web_touch_event_traits.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/notification_types.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/input_event_ack_state.h" |
| #include "ipc/ipc_sender.h" |
| #include "services/tracing/public/cpp/perfetto/flow_event_utils.h" |
| #include "third_party/blink/public/common/input/web_coalesced_input_event.h" |
| #include "third_party/blink/public/mojom/input/input_event_result.mojom-shared.h" |
| #include "third_party/blink/public/mojom/input/input_handler.mojom-forward.h" |
| #include "third_party/blink/public/mojom/input/input_handler.mojom-shared.h" |
| #include "third_party/blink/public/mojom/input/touch_event.mojom.h" |
| #include "ui/events/blink/blink_event_util.h" |
| #include "ui/events/blink/blink_features.h" |
| #include "ui/events/blink/did_overscroll_params.h" |
| #include "ui/events/blink/web_input_event_traits.h" |
| #include "ui/events/event.h" |
| #include "ui/events/keycodes/keyboard_codes.h" |
| |
| namespace content { |
| |
| using blink::WebGestureEvent; |
| using blink::WebInputEvent; |
| using blink::WebKeyboardEvent; |
| using blink::WebMouseEvent; |
| using blink::WebMouseWheelEvent; |
| using blink::WebTouchEvent; |
| using perfetto::protos::pbzero::ChromeLatencyInfo; |
| using perfetto::protos::pbzero::TrackEvent; |
| using ui::WebInputEventTraits; |
| |
| namespace { |
| |
| bool WasHandled(blink::mojom::InputEventResultState state) { |
| switch (state) { |
| case blink::mojom::InputEventResultState::kConsumed: |
| case blink::mojom::InputEventResultState::kNoConsumerExists: |
| case blink::mojom::InputEventResultState::kUnknown: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| std::unique_ptr<blink::WebCoalescedInputEvent> ScaleEvent( |
| const WebInputEvent& event, |
| double scale, |
| const ui::LatencyInfo& latency_info) { |
| std::unique_ptr<blink::WebInputEvent> event_in_viewport = |
| ui::ScaleWebInputEvent(event, scale); |
| return std::make_unique<blink::WebCoalescedInputEvent>( |
| event_in_viewport ? std::move(event_in_viewport) : event.Clone(), |
| std::vector<std::unique_ptr<WebInputEvent>>(), |
| std::vector<std::unique_ptr<WebInputEvent>>(), latency_info); |
| } |
| |
| } // namespace |
| |
| InputRouterImpl::InputRouterImpl( |
| InputRouterImplClient* client, |
| InputDispositionHandler* disposition_handler, |
| FlingControllerSchedulerClient* fling_scheduler_client, |
| const Config& config) |
| : client_(client), |
| disposition_handler_(disposition_handler), |
| touch_scroll_started_sent_(false), |
| wheel_event_queue_(this), |
| touch_event_queue_(this, config.touch_config), |
| touchpad_pinch_event_queue_(this), |
| gesture_event_queue_(this, |
| this, |
| fling_scheduler_client, |
| config.gesture_config), |
| device_scale_factor_(1.f) { |
| weak_this_ = weak_ptr_factory_.GetWeakPtr(); |
| |
| DCHECK(client); |
| DCHECK(disposition_handler); |
| DCHECK(fling_scheduler_client); |
| UpdateTouchAckTimeoutEnabled(); |
| } |
| |
| InputRouterImpl::~InputRouterImpl() {} |
| |
| void InputRouterImpl::SendMouseEvent( |
| const MouseEventWithLatencyInfo& mouse_event, |
| MouseEventCallback event_result_callback) { |
| if ((mouse_event.event.GetType() == WebInputEvent::Type::kMouseDown && |
| gesture_event_queue_.GetTouchpadTapSuppressionController() |
| ->ShouldSuppressMouseDown(mouse_event)) || |
| (mouse_event.event.GetType() == WebInputEvent::Type::kMouseUp && |
| gesture_event_queue_.GetTouchpadTapSuppressionController() |
| ->ShouldSuppressMouseUp())) { |
| std::move(event_result_callback) |
| .Run(mouse_event, blink::mojom::InputEventResultSource::kBrowser, |
| blink::mojom::InputEventResultState::kIgnored); |
| return; |
| } |
| |
| SendMouseEventImmediately(mouse_event, std::move(event_result_callback)); |
| } |
| |
| void InputRouterImpl::SendWheelEvent( |
| const MouseWheelEventWithLatencyInfo& wheel_event) { |
| wheel_event_queue_.QueueEvent(wheel_event); |
| } |
| |
| void InputRouterImpl::SendKeyboardEvent( |
| const NativeWebKeyboardEventWithLatencyInfo& key_event, |
| KeyboardEventCallback event_result_callback) { |
| gesture_event_queue_.StopFling(); |
| blink::mojom::WidgetInputHandler::DispatchEventCallback callback = |
| base::BindOnce(&InputRouterImpl::KeyboardEventHandled, weak_this_, |
| key_event, std::move(event_result_callback)); |
| FilterAndSendWebInputEvent(key_event.event, key_event.latency, |
| std::move(callback)); |
| } |
| |
| void InputRouterImpl::SendGestureEvent( |
| const GestureEventWithLatencyInfo& original_gesture_event) { |
| TRACE_EVENT0("input", "InputRouterImpl::SendGestureEvent"); |
| input_stream_validator_.Validate(original_gesture_event.event); |
| |
| GestureEventWithLatencyInfo gesture_event(original_gesture_event); |
| |
| if (gesture_event_queue_.PassToFlingController(gesture_event)) { |
| TRACE_EVENT_INSTANT0("input", "FilteredForFling", TRACE_EVENT_SCOPE_THREAD); |
| disposition_handler_->OnGestureEventAck( |
| gesture_event, blink::mojom::InputEventResultSource::kBrowser, |
| blink::mojom::InputEventResultState::kConsumed, |
| /*scroll_result_data=*/nullptr); |
| return; |
| } |
| |
| FilterGestureEventResult result = |
| touch_action_filter_.FilterGestureEvent(&gesture_event.event); |
| if (result == FilterGestureEventResult::kFilterGestureEventDelayed) { |
| TRACE_EVENT_INSTANT0("input", "DeferredForTouchAction", |
| TRACE_EVENT_SCOPE_THREAD); |
| gesture_event_queue_.QueueDeferredEvents(gesture_event); |
| return; |
| } |
| SendGestureEventWithoutQueueing(gesture_event, result); |
| } |
| |
| void InputRouterImpl::SendGestureEventWithoutQueueing( |
| GestureEventWithLatencyInfo& gesture_event, |
| const FilterGestureEventResult& existing_result) { |
| TRACE_EVENT0("input", "InputRouterImpl::SendGestureEventWithoutQueueing"); |
| DCHECK_NE(existing_result, |
| FilterGestureEventResult::kFilterGestureEventDelayed); |
| if (existing_result == |
| FilterGestureEventResult::kFilterGestureEventFiltered) { |
| TRACE_EVENT_INSTANT0("input", "FilteredForTouchAction", |
| TRACE_EVENT_SCOPE_THREAD); |
| disposition_handler_->OnGestureEventAck( |
| gesture_event, blink::mojom::InputEventResultSource::kBrowser, |
| blink::mojom::InputEventResultState::kConsumed, |
| /*scroll_result_data=*/nullptr); |
| return; |
| } |
| |
| // Handle scroll gesture events for stylus writing. If we could not start |
| // writing for any reason, we should not filter the scroll events. |
| if (HandleGestureScrollForStylusWriting(gesture_event.event)) { |
| disposition_handler_->OnGestureEventAck( |
| gesture_event, blink::mojom::InputEventResultSource::kBrowser, |
| blink::mojom::InputEventResultState::kConsumed, |
| /*scroll_result_data=*/nullptr); |
| return; |
| } |
| |
| wheel_event_queue_.OnGestureScrollEvent(gesture_event); |
| |
| if (gesture_event.event.SourceDevice() == |
| blink::WebGestureDevice::kTouchscreen) { |
| if (gesture_event.event.GetType() == |
| blink::WebInputEvent::Type::kGestureScrollBegin) { |
| touch_scroll_started_sent_ = false; |
| } else if (!touch_scroll_started_sent_ && |
| gesture_event.event.GetType() == |
| blink::WebInputEvent::Type::kGestureScrollUpdate) { |
| // A touch scroll hasn't really started until the first |
| // GestureScrollUpdate event. Eg. if the page consumes all touchmoves |
| // then no scrolling really ever occurs (even though we still send |
| // GestureScrollBegin). |
| touch_scroll_started_sent_ = true; |
| touch_event_queue_.PrependTouchScrollNotification(); |
| } |
| } |
| |
| if (gesture_event.event.IsTouchpadZoomEvent() && |
| gesture_event.event.NeedsWheelEvent()) { |
| touchpad_pinch_event_queue_.QueueEvent(gesture_event); |
| return; |
| } |
| |
| if (!gesture_event_queue_.DebounceOrForwardEvent(gesture_event)) { |
| TRACE_EVENT_INSTANT0("input", "FilteredForDebounce", |
| TRACE_EVENT_SCOPE_THREAD); |
| disposition_handler_->OnGestureEventAck( |
| gesture_event, blink::mojom::InputEventResultSource::kBrowser, |
| blink::mojom::InputEventResultState::kConsumed, |
| /*scroll_result_data=*/nullptr); |
| } |
| } |
| |
| bool InputRouterImpl::HandleGestureScrollForStylusWriting( |
| const blink::WebGestureEvent& event) { |
| switch (event.GetType()) { |
| case WebInputEvent::Type::kGestureScrollBegin: { |
| if (event.data.scroll_begin.pointer_count != 1) |
| break; |
| |
| const float& deltaXHint = event.data.scroll_begin.delta_x_hint; |
| const float& deltaYHint = event.data.scroll_begin.delta_y_hint; |
| if (deltaXHint == 0.0 && deltaYHint == 0.0) |
| break; |
| |
| if (!client_->GetRenderWidgetHostViewBase()) |
| break; |
| |
| absl::optional<cc::TouchAction> allowed_touch_action = |
| AllowedTouchAction(); |
| // Don't handle for non-writable areas as kInternalNotWritable bit is set. |
| if (!allowed_touch_action.has_value() || |
| (allowed_touch_action.value() & |
| cc::TouchAction::kInternalNotWritable) == |
| cc::TouchAction::kInternalNotWritable) |
| break; |
| |
| // Request to start stylus writing as we have detected stylus writing |
| // movement, and treat scroll gesture as stylus input if started. |
| if (client_->GetRenderWidgetHostViewBase()->RequestStartStylusWriting()) { |
| stylus_writing_started_ = true; |
| // The below call is done to Focus the stylus writable input element. |
| client_->OnStartStylusWriting(); |
| return true; |
| } |
| break; |
| } |
| case WebInputEvent::Type::kGestureScrollUpdate: |
| // TODO(crbug.com/1330817): Pass the queued scroll delta to stylus |
| // writing recognition system. |
| return stylus_writing_started_; |
| case WebInputEvent::Type::kGestureScrollEnd: { |
| // When stylus writing starts, Touch Move events would be forwarded to |
| // stylus recognition system and gesture detection would be reset. We |
| // would receive the GestureScrollEnd here after that. |
| if (stylus_writing_started_) { |
| stylus_writing_started_ = false; |
| return true; |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| return false; |
| } |
| |
| void InputRouterImpl::SendTouchEvent( |
| const TouchEventWithLatencyInfo& touch_event) { |
| TouchEventWithLatencyInfo updated_touch_event = touch_event; |
| SetMovementXYForTouchPoints(&updated_touch_event.event); |
| input_stream_validator_.Validate(updated_touch_event.event); |
| touch_event_queue_.QueueEvent(updated_touch_event); |
| } |
| |
| void InputRouterImpl::NotifySiteIsMobileOptimized(bool is_mobile_optimized) { |
| touch_event_queue_.SetIsMobileOptimizedSite(is_mobile_optimized); |
| } |
| |
| bool InputRouterImpl::HasPendingEvents() const { |
| return !touch_event_queue_.Empty() || !gesture_event_queue_.empty() || |
| wheel_event_queue_.has_pending() || |
| touchpad_pinch_event_queue_.has_pending(); |
| } |
| |
| void InputRouterImpl::SetDeviceScaleFactor(float device_scale_factor) { |
| device_scale_factor_ = device_scale_factor; |
| } |
| |
| void InputRouterImpl::SetForceEnableZoom(bool enabled) { |
| touch_action_filter_.SetForceEnableZoom(enabled); |
| } |
| |
| absl::optional<cc::TouchAction> InputRouterImpl::AllowedTouchAction() { |
| return touch_action_filter_.allowed_touch_action(); |
| } |
| |
| absl::optional<cc::TouchAction> InputRouterImpl::ActiveTouchAction() { |
| return touch_action_filter_.active_touch_action(); |
| } |
| |
| mojo::PendingRemote<blink::mojom::WidgetInputHandlerHost> |
| InputRouterImpl::BindNewHost( |
| scoped_refptr<base::SequencedTaskRunner> task_runner) { |
| host_receiver_.reset(); |
| return host_receiver_.BindNewPipeAndPassRemote(task_runner); |
| } |
| |
| void InputRouterImpl::StopFling() { |
| gesture_event_queue_.StopFling(); |
| } |
| |
| void InputRouterImpl::ProcessDeferredGestureEventQueue() { |
| TRACE_EVENT0("input", "InputRouterImpl::ProcessDeferredGestureEventQueue"); |
| GestureEventQueue::GestureQueue deferred_gesture_events = |
| gesture_event_queue_.TakeDeferredEvents(); |
| for (auto& it : deferred_gesture_events) { |
| FilterGestureEventResult result = |
| touch_action_filter_.FilterGestureEvent(&(it.event)); |
| SendGestureEventWithoutQueueing(it, result); |
| } |
| } |
| |
| void InputRouterImpl::SetTouchActionFromMain(cc::TouchAction touch_action) { |
| TRACE_EVENT1("input", "InputRouterImpl::SetTouchActionFromMain", |
| "touch_action", TouchActionToString(touch_action)); |
| touch_action_filter_.OnSetTouchAction(touch_action); |
| touch_event_queue_.StopTimeoutMonitor(); |
| ProcessDeferredGestureEventQueue(); |
| UpdateTouchAckTimeoutEnabled(); |
| } |
| |
| void InputRouterImpl::SetPanAction(blink::mojom::PanAction pan_action) { |
| if (pan_action_ == pan_action) |
| return; |
| pan_action_ = pan_action; |
| |
| // TODO(mahesh.ma): Update PanAction state to view, once RenderWidgetHostView |
| // is set again. |
| if (!client_->GetRenderWidgetHostViewBase()) |
| return; |
| client_->GetRenderWidgetHostViewBase()->SetHoverActionStylusWritable( |
| pan_action_ == blink::mojom::PanAction::kStylusWritable); |
| } |
| |
| void InputRouterImpl::OnSetCompositorAllowedTouchAction( |
| cc::TouchAction touch_action) { |
| TRACE_EVENT1("input", "InputRouterImpl::OnSetCompositorAllowedTouchAction", |
| "action", cc::TouchActionToString(touch_action)); |
| touch_action_filter_.OnSetCompositorAllowedTouchAction(touch_action); |
| client_->OnSetCompositorAllowedTouchAction(touch_action); |
| if (touch_action == cc::TouchAction::kAuto) |
| FlushDeferredGestureQueue(); |
| UpdateTouchAckTimeoutEnabled(); |
| } |
| |
| void InputRouterImpl::DidOverscroll( |
| blink::mojom::DidOverscrollParamsPtr params) { |
| // Touchpad and Touchscreen flings are handled on the browser side. |
| ui::DidOverscrollParams fling_updated_params = { |
| params->accumulated_overscroll, params->latest_overscroll_delta, |
| params->current_fling_velocity, params->causal_event_viewport_point, |
| params->overscroll_behavior}; |
| fling_updated_params.current_fling_velocity = |
| gesture_event_queue_.CurrentFlingVelocity(); |
| client_->DidOverscroll(fling_updated_params); |
| } |
| |
| void InputRouterImpl::DidStartScrollingViewport() { |
| client_->DidStartScrollingViewport(); |
| } |
| |
| void InputRouterImpl::ImeCancelComposition() { |
| client_->OnImeCancelComposition(); |
| } |
| |
| void InputRouterImpl::ImeCompositionRangeChanged( |
| const gfx::Range& range, |
| const std::vector<gfx::Rect>& bounds) { |
| client_->OnImeCompositionRangeChanged(range, bounds); |
| } |
| |
| void InputRouterImpl::SetMouseCapture(bool capture) { |
| client_->SetMouseCapture(capture); |
| } |
| |
| void InputRouterImpl::RequestMouseLock(bool from_user_gesture, |
| bool unadjusted_movement, |
| RequestMouseLockCallback response) { |
| client_->RequestMouseLock(from_user_gesture, unadjusted_movement, |
| std::move(response)); |
| } |
| |
| void InputRouterImpl::SetMovementXYForTouchPoints(blink::WebTouchEvent* event) { |
| for (size_t i = 0; i < event->touches_length; ++i) { |
| blink::WebTouchPoint* touch_point = &event->touches[i]; |
| if (touch_point->state == blink::WebTouchPoint::State::kStateMoved) { |
| const gfx::Point& last_position = global_touch_position_[touch_point->id]; |
| touch_point->movement_x = |
| touch_point->PositionInScreen().x() - last_position.x(); |
| touch_point->movement_y = |
| touch_point->PositionInScreen().y() - last_position.y(); |
| global_touch_position_[touch_point->id].SetPoint( |
| touch_point->PositionInScreen().x(), |
| touch_point->PositionInScreen().y()); |
| } else { |
| touch_point->movement_x = 0; |
| touch_point->movement_y = 0; |
| if (touch_point->state == blink::WebTouchPoint::State::kStateReleased || |
| touch_point->state == blink::WebTouchPoint::State::kStateCancelled) { |
| global_touch_position_.erase(touch_point->id); |
| } else if (touch_point->state == |
| blink::WebTouchPoint::State::kStatePressed) { |
| DCHECK(global_touch_position_.find(touch_point->id) == |
| global_touch_position_.end()); |
| global_touch_position_[touch_point->id] = |
| gfx::Point(touch_point->PositionInScreen().x(), |
| touch_point->PositionInScreen().y()); |
| } |
| } |
| } |
| } |
| |
| // Forwards MouseEvent without passing it through |
| // TouchpadTapSuppressionController. |
| void InputRouterImpl::SendMouseEventImmediately( |
| const MouseEventWithLatencyInfo& mouse_event, |
| MouseEventCallback event_result_callback) { |
| blink::mojom::WidgetInputHandler::DispatchEventCallback callback = |
| base::BindOnce(&InputRouterImpl::MouseEventHandled, weak_this_, |
| mouse_event, std::move(event_result_callback)); |
| FilterAndSendWebInputEvent(mouse_event.event, mouse_event.latency, |
| std::move(callback)); |
| } |
| |
| void InputRouterImpl::SendTouchEventImmediately( |
| const TouchEventWithLatencyInfo& touch_event) { |
| blink::mojom::WidgetInputHandler::DispatchEventCallback callback = |
| base::BindOnce(&InputRouterImpl::TouchEventHandled, weak_this_, |
| touch_event); |
| FilterAndSendWebInputEvent(touch_event.event, touch_event.latency, |
| std::move(callback)); |
| } |
| |
| void InputRouterImpl::FlushDeferredGestureQueue() { |
| touch_action_filter_.OnSetTouchAction(cc::TouchAction::kAuto); |
| ProcessDeferredGestureEventQueue(); |
| } |
| |
| void InputRouterImpl::OnTouchEventAck( |
| const TouchEventWithLatencyInfo& event, |
| blink::mojom::InputEventResultSource ack_source, |
| blink::mojom::InputEventResultState ack_result) { |
| if (WebTouchEventTraits::IsTouchSequenceStart(event.event)) { |
| touch_action_filter_.AppendToGestureSequenceForDebugging("T"); |
| touch_action_filter_.AppendToGestureSequenceForDebugging( |
| base::NumberToString(static_cast<uint32_t>(ack_result)).c_str()); |
| touch_action_filter_.AppendToGestureSequenceForDebugging( |
| base::NumberToString(event.event.unique_touch_event_id).c_str()); |
| touch_action_filter_.IncreaseActiveTouches(); |
| } |
| disposition_handler_->OnTouchEventAck(event, ack_source, ack_result); |
| |
| if (WebTouchEventTraits::IsTouchSequenceEnd(event.event)) { |
| touch_action_filter_.AppendToGestureSequenceForDebugging("E"); |
| touch_action_filter_.AppendToGestureSequenceForDebugging( |
| base::NumberToString(event.event.unique_touch_event_id).c_str()); |
| touch_action_filter_.DecreaseActiveTouches(); |
| touch_action_filter_.ReportAndResetTouchAction(); |
| UpdateTouchAckTimeoutEnabled(); |
| } |
| } |
| |
| void InputRouterImpl::OnFilteringTouchEvent(const WebTouchEvent& touch_event) { |
| // The event stream given to the renderer is not guaranteed to be |
| // valid based on the current TouchEventStreamValidator rules. This event will |
| // never be given to the renderer, but in order to ensure that the event |
| // stream |output_stream_validator_| sees is valid, we give events which are |
| // filtered out to the validator. crbug.com/589111 proposes adding an |
| // additional validator for the events which are actually sent to the |
| // renderer. |
| output_stream_validator_.Validate(touch_event); |
| } |
| |
| void InputRouterImpl::SendGestureEventImmediately( |
| const GestureEventWithLatencyInfo& gesture_event) { |
| blink::mojom::WidgetInputHandler::DispatchEventCallback callback = |
| base::BindOnce(&InputRouterImpl::GestureEventHandled, weak_this_, |
| gesture_event); |
| FilterAndSendWebInputEvent(gesture_event.event, gesture_event.latency, |
| std::move(callback)); |
| } |
| |
| void InputRouterImpl::OnGestureEventAck( |
| const GestureEventWithLatencyInfo& event, |
| blink::mojom::InputEventResultSource ack_source, |
| blink::mojom::InputEventResultState ack_result, |
| blink::mojom::ScrollResultDataPtr scroll_result_data) { |
| touch_event_queue_.OnGestureEventAck(event, ack_result); |
| disposition_handler_->OnGestureEventAck(event, ack_source, ack_result, |
| std::move(scroll_result_data)); |
| } |
| |
| void InputRouterImpl::SendGeneratedWheelEvent( |
| const MouseWheelEventWithLatencyInfo& wheel_event) { |
| client_->ForwardWheelEventWithLatencyInfo(wheel_event.event, |
| wheel_event.latency); |
| } |
| |
| void InputRouterImpl::SendGeneratedGestureScrollEvents( |
| const GestureEventWithLatencyInfo& gesture_event) { |
| client_->ForwardGestureEventWithLatencyInfo(gesture_event.event, |
| gesture_event.latency); |
| } |
| |
| gfx::Size InputRouterImpl::GetRootWidgetViewportSize() { |
| return client_->GetRootWidgetViewportSize(); |
| } |
| |
| void InputRouterImpl::SendMouseWheelEventImmediately( |
| const MouseWheelEventWithLatencyInfo& wheel_event, |
| MouseWheelEventQueueClient::MouseWheelEventHandledCallback |
| callee_callback) { |
| blink::mojom::WidgetInputHandler::DispatchEventCallback callback = |
| base::BindOnce(&InputRouterImpl::MouseWheelEventHandled, weak_this_, |
| wheel_event, std::move(callee_callback)); |
| FilterAndSendWebInputEvent(wheel_event.event, wheel_event.latency, |
| std::move(callback)); |
| } |
| |
| void InputRouterImpl::OnMouseWheelEventAck( |
| const MouseWheelEventWithLatencyInfo& event, |
| blink::mojom::InputEventResultSource ack_source, |
| blink::mojom::InputEventResultState ack_result) { |
| disposition_handler_->OnWheelEventAck(event, ack_source, ack_result); |
| gesture_event_queue_.OnWheelEventAck(event, ack_source, ack_result); |
| } |
| |
| void InputRouterImpl::ForwardGestureEventWithLatencyInfo( |
| const blink::WebGestureEvent& event, |
| const ui::LatencyInfo& latency_info) { |
| client_->ForwardGestureEventWithLatencyInfo(event, latency_info); |
| } |
| |
| void InputRouterImpl::SendMouseWheelEventForPinchImmediately( |
| const MouseWheelEventWithLatencyInfo& event, |
| TouchpadPinchEventQueueClient::MouseWheelEventHandledCallback callback) { |
| SendMouseWheelEventImmediately(event, std::move(callback)); |
| } |
| |
| void InputRouterImpl::OnGestureEventForPinchAck( |
| const GestureEventWithLatencyInfo& event, |
| blink::mojom::InputEventResultSource ack_source, |
| blink::mojom::InputEventResultState ack_result) { |
| OnGestureEventAck(event, ack_source, ack_result, |
| /*scroll_result_data=*/nullptr); |
| } |
| |
| bool InputRouterImpl::IsWheelScrollInProgress() { |
| return client_->IsWheelScrollInProgress(); |
| } |
| |
| bool InputRouterImpl::IsAutoscrollInProgress() { |
| return client_->IsAutoscrollInProgress(); |
| } |
| |
| void InputRouterImpl::FilterAndSendWebInputEvent( |
| const WebInputEvent& input_event, |
| const ui::LatencyInfo& latency_info, |
| blink::mojom::WidgetInputHandler::DispatchEventCallback callback) { |
| TRACE_EVENT1("input", "InputRouterImpl::FilterAndSendWebInputEvent", "type", |
| WebInputEvent::GetName(input_event.GetType())); |
| TRACE_EVENT("input,benchmark,devtools.timeline", "LatencyInfo.Flow", |
| [&latency_info](perfetto::EventContext ctx) { |
| ChromeLatencyInfo* info = |
| ctx.event()->set_chrome_latency_info(); |
| info->set_trace_id(latency_info.trace_id()); |
| info->set_step(ChromeLatencyInfo::STEP_SEND_INPUT_EVENT_UI); |
| |
| tracing::FillFlowEvent(ctx, |
| perfetto::protos::pbzero::TrackEvent:: |
| LegacyEvent::FLOW_INOUT, |
| latency_info.trace_id()); |
| }); |
| |
| output_stream_validator_.Validate(input_event); |
| blink::mojom::InputEventResultState filtered_state = |
| client_->FilterInputEvent(input_event, latency_info); |
| if (WasHandled(filtered_state)) { |
| TRACE_EVENT_INSTANT0("input", "InputEventFiltered", |
| TRACE_EVENT_SCOPE_THREAD); |
| if (filtered_state != blink::mojom::InputEventResultState::kUnknown) { |
| std::move(callback).Run(blink::mojom::InputEventResultSource::kBrowser, |
| latency_info, filtered_state, nullptr, nullptr, |
| /*scroll_result_data=*/nullptr); |
| } |
| return; |
| } |
| |
| std::unique_ptr<blink::WebCoalescedInputEvent> event = |
| ScaleEvent(input_event, device_scale_factor_, latency_info); |
| if (WebInputEventTraits::ShouldBlockEventStream(input_event)) { |
| TRACE_EVENT_INSTANT0("input", "InputEventSentBlocking", |
| TRACE_EVENT_SCOPE_THREAD); |
| client_->IncrementInFlightEventCount(); |
| blink::mojom::WidgetInputHandler::DispatchEventCallback renderer_callback = |
| base::BindOnce( |
| [](blink::mojom::WidgetInputHandler::DispatchEventCallback callback, |
| base::WeakPtr<InputRouterImpl> input_router, |
| blink::mojom::InputEventResultSource source, |
| const ui::LatencyInfo& latency, |
| blink::mojom::InputEventResultState state, |
| blink::mojom::DidOverscrollParamsPtr overscroll, |
| blink::mojom::TouchActionOptionalPtr touch_action, |
| blink::mojom::ScrollResultDataPtr scroll_result_data) { |
| // Filter source to ensure only valid values are sent from the |
| // renderer. |
| if (source == blink::mojom::InputEventResultSource::kBrowser) { |
| if (input_router) |
| input_router->client_->OnInvalidInputEventSource(); |
| return; |
| } |
| |
| std::move(callback).Run( |
| source, latency, state, std::move(overscroll), |
| std::move(touch_action), std::move(scroll_result_data)); |
| }, |
| std::move(callback), weak_this_); |
| client_->GetWidgetInputHandler()->DispatchEvent( |
| std::move(event), std::move(renderer_callback)); |
| } else { |
| TRACE_EVENT_INSTANT0("input", "InputEventSentNonBlocking", |
| TRACE_EVENT_SCOPE_THREAD); |
| client_->GetWidgetInputHandler()->DispatchNonBlockingEvent( |
| std::move(event)); |
| std::move(callback).Run(blink::mojom::InputEventResultSource::kBrowser, |
| latency_info, |
| blink::mojom::InputEventResultState::kIgnored, |
| nullptr, nullptr, /*scroll_result_data=*/nullptr); |
| } |
| } |
| |
| void InputRouterImpl::KeyboardEventHandled( |
| const NativeWebKeyboardEventWithLatencyInfo& event, |
| KeyboardEventCallback event_result_callback, |
| blink::mojom::InputEventResultSource source, |
| const ui::LatencyInfo& latency, |
| blink::mojom::InputEventResultState state, |
| blink::mojom::DidOverscrollParamsPtr overscroll, |
| blink::mojom::TouchActionOptionalPtr touch_action, |
| blink::mojom::ScrollResultDataPtr scroll_result_data) { |
| TRACE_EVENT2("input", "InputRouterImpl::KeyboardEventHandled", "type", |
| WebInputEvent::GetName(event.event.GetType()), "ack", |
| InputEventResultStateToString(state)); |
| |
| if (source != blink::mojom::InputEventResultSource::kBrowser) |
| client_->DecrementInFlightEventCount(source); |
| event.latency.AddNewLatencyFrom(latency); |
| std::move(event_result_callback).Run(event, source, state); |
| |
| // WARNING: This InputRouterImpl can be deallocated at this point |
| // (i.e. in the case of Ctrl+W, where the call to |
| // HandleKeyboardEvent destroys this InputRouterImpl). |
| // TODO(jdduke): crbug.com/274029 - Make ack-triggered shutdown async. |
| } |
| |
| void InputRouterImpl::MouseEventHandled( |
| const MouseEventWithLatencyInfo& event, |
| MouseEventCallback event_result_callback, |
| blink::mojom::InputEventResultSource source, |
| const ui::LatencyInfo& latency, |
| blink::mojom::InputEventResultState state, |
| blink::mojom::DidOverscrollParamsPtr overscroll, |
| blink::mojom::TouchActionOptionalPtr touch_action, |
| blink::mojom::ScrollResultDataPtr scroll_result_data) { |
| TRACE_EVENT2("input", "InputRouterImpl::MouseEventHandled", "type", |
| WebInputEvent::GetName(event.event.GetType()), "ack", |
| InputEventResultStateToString(state)); |
| |
| if (source != blink::mojom::InputEventResultSource::kBrowser) |
| client_->DecrementInFlightEventCount(source); |
| event.latency.AddNewLatencyFrom(latency); |
| std::move(event_result_callback).Run(event, source, state); |
| } |
| |
| void InputRouterImpl::TouchEventHandled( |
| const TouchEventWithLatencyInfo& touch_event, |
| blink::mojom::InputEventResultSource source, |
| const ui::LatencyInfo& latency, |
| blink::mojom::InputEventResultState state, |
| blink::mojom::DidOverscrollParamsPtr overscroll, |
| blink::mojom::TouchActionOptionalPtr touch_action, |
| blink::mojom::ScrollResultDataPtr scroll_result_data) { |
| TRACE_EVENT2("input", "InputRouterImpl::TouchEventHandled", "type", |
| WebInputEvent::GetName(touch_event.event.GetType()), "ack", |
| InputEventResultStateToString(state)); |
| if (source != blink::mojom::InputEventResultSource::kBrowser) |
| client_->DecrementInFlightEventCount(source); |
| touch_event.latency.AddNewLatencyFrom(latency); |
| |
| // The SetTouchAction IPC occurs on a different channel so always |
| // send it in the input event ack to ensure it is available at the |
| // time the ACK is handled. |
| if (touch_action) { |
| // For main thread ACKs, Blink will directly call SetTouchActionFromMain. |
| if (source == blink::mojom::InputEventResultSource::kCompositorThread) |
| OnSetCompositorAllowedTouchAction(touch_action->touch_action); |
| } |
| |
| // TODO(crbug.com/953547): find a proper way to stop the timeout monitor. |
| bool should_stop_timeout_monitor = true; |
| // |touch_event_queue_| will forward to OnTouchEventAck when appropriate. |
| touch_event_queue_.ProcessTouchAck(source, state, latency, |
| touch_event.event.unique_touch_event_id, |
| should_stop_timeout_monitor); |
| } |
| |
| void InputRouterImpl::GestureEventHandled( |
| const GestureEventWithLatencyInfo& gesture_event, |
| blink::mojom::InputEventResultSource source, |
| const ui::LatencyInfo& latency, |
| blink::mojom::InputEventResultState state, |
| blink::mojom::DidOverscrollParamsPtr overscroll, |
| blink::mojom::TouchActionOptionalPtr touch_action, |
| blink::mojom::ScrollResultDataPtr scroll_result_data) { |
| TRACE_EVENT2("input", "InputRouterImpl::GestureEventHandled", "type", |
| WebInputEvent::GetName(gesture_event.event.GetType()), "ack", |
| InputEventResultStateToString(state)); |
| if (source != blink::mojom::InputEventResultSource::kBrowser) |
| client_->DecrementInFlightEventCount(source); |
| |
| if (overscroll) { |
| DCHECK_EQ(WebInputEvent::Type::kGestureScrollUpdate, |
| gesture_event.event.GetType()); |
| DidOverscroll(std::move(overscroll)); |
| } |
| |
| // |gesture_event_queue_| will forward to OnGestureEventAck when appropriate. |
| gesture_event_queue_.ProcessGestureAck(source, state, |
| gesture_event.event.GetType(), latency, |
| std::move(scroll_result_data)); |
| } |
| |
| void InputRouterImpl::MouseWheelEventHandled( |
| const MouseWheelEventWithLatencyInfo& event, |
| MouseWheelEventQueueClient::MouseWheelEventHandledCallback callback, |
| blink::mojom::InputEventResultSource source, |
| const ui::LatencyInfo& latency, |
| blink::mojom::InputEventResultState state, |
| blink::mojom::DidOverscrollParamsPtr overscroll, |
| blink::mojom::TouchActionOptionalPtr touch_action, |
| blink::mojom::ScrollResultDataPtr scroll_result_data) { |
| TRACE_EVENT2("input", "InputRouterImpl::MouseWheelEventHandled", "type", |
| WebInputEvent::GetName(event.event.GetType()), "ack", |
| InputEventResultStateToString(state)); |
| if (source != blink::mojom::InputEventResultSource::kBrowser) |
| client_->DecrementInFlightEventCount(source); |
| event.latency.AddNewLatencyFrom(latency); |
| |
| if (overscroll) |
| DidOverscroll(std::move(overscroll)); |
| |
| std::move(callback).Run(event, source, state); |
| } |
| |
| void InputRouterImpl::OnHasTouchEventConsumers( |
| blink::mojom::TouchEventConsumersPtr consumers) { |
| TRACE_EVENT1("input", "InputRouterImpl::OnHasTouchEventHandlers", |
| "has_handlers", consumers->has_touch_event_handlers); |
| |
| touch_action_filter_.OnHasTouchEventHandlers( |
| consumers->has_touch_event_handlers); |
| touch_event_queue_.OnHasTouchEventHandlers( |
| consumers->has_touch_event_handlers || |
| consumers->has_hit_testable_scrollbar); |
| } |
| |
| void InputRouterImpl::WaitForInputProcessed(base::OnceClosure callback) { |
| // TODO(bokan): Some kinds of input is queued in one of the various queues |
| // available in this class. To be truly robust, we should wait until those |
| // queues are flushed before issuing this message. This will be done in a |
| // follow-up. https://crbug.com/902446. |
| client_->GetWidgetInputHandler()->WaitForInputProcessed(std::move(callback)); |
| } |
| |
| void InputRouterImpl::FlushTouchEventQueue() { |
| touch_event_queue_.FlushQueue(); |
| } |
| |
| void InputRouterImpl::ForceSetTouchActionAuto() { |
| touch_action_filter_.AppendToGestureSequenceForDebugging("F"); |
| touch_action_filter_.OnSetTouchAction(cc::TouchAction::kAuto); |
| // TODO(xidachen): Call FlushDeferredGestureQueue when this flag is enabled. |
| touch_event_queue_.StopTimeoutMonitor(); |
| ProcessDeferredGestureEventQueue(); |
| } |
| |
| void InputRouterImpl::ForceResetTouchActionForTest() { |
| touch_action_filter_.ForceResetTouchActionForTest(); |
| } |
| |
| bool InputRouterImpl::IsFlingActiveForTest() { |
| return gesture_event_queue_.IsFlingActiveForTest(); |
| } |
| |
| void InputRouterImpl::UpdateTouchAckTimeoutEnabled() { |
| // TouchAction::kNone will prevent scrolling, in which case the timeout serves |
| // little purpose. It's also a strong signal that touch handling is critical |
| // to page functionality, so the timeout could do more harm than good. |
| absl::optional<cc::TouchAction> allowed_touch_action = |
| touch_action_filter_.allowed_touch_action(); |
| cc::TouchAction compositor_allowed_touch_action = |
| touch_action_filter_.compositor_allowed_touch_action(); |
| const bool touch_ack_timeout_disabled = |
| (allowed_touch_action.has_value() && |
| allowed_touch_action.value() == cc::TouchAction::kNone) || |
| (compositor_allowed_touch_action == cc::TouchAction::kNone); |
| touch_event_queue_.SetAckTimeoutEnabled(!touch_ack_timeout_disabled); |
| } |
| |
| } // namespace content |