| // 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/renderer/input/render_widget_input_handler.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <utility> |
| |
| #include "base/auto_reset.h" |
| #include "base/command_line.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/trace_event/trace_event_synthetic_delay.h" |
| #include "build/build_config.h" |
| #include "cc/trees/swap_promise_monitor.h" |
| #include "content/common/input/input_event_ack.h" |
| #include "content/common/input/input_event_ack_state.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/renderer/gpu/render_widget_compositor.h" |
| #include "content/renderer/ime_event_guard.h" |
| #include "content/renderer/input/render_widget_input_handler_delegate.h" |
| #include "content/renderer/render_thread_impl.h" |
| #include "content/renderer/render_widget.h" |
| #include "third_party/WebKit/public/platform/WebFloatPoint.h" |
| #include "third_party/WebKit/public/platform/WebFloatSize.h" |
| #include "third_party/WebKit/public/platform/WebGestureEvent.h" |
| #include "third_party/WebKit/public/platform/WebKeyboardEvent.h" |
| #include "third_party/WebKit/public/platform/WebMouseWheelEvent.h" |
| #include "third_party/WebKit/public/platform/WebTouchEvent.h" |
| #include "third_party/WebKit/public/platform/scheduler/renderer/renderer_scheduler.h" |
| #include "ui/events/blink/web_input_event_traits.h" |
| #include "ui/gfx/geometry/point_conversions.h" |
| #include "ui/latency/latency_info.h" |
| |
| #if defined(OS_ANDROID) |
| #include <android/keycodes.h> |
| #endif |
| |
| using blink::WebFloatPoint; |
| using blink::WebFloatSize; |
| using blink::WebGestureEvent; |
| using blink::WebInputEvent; |
| using blink::WebInputEventResult; |
| using blink::WebKeyboardEvent; |
| using blink::WebMouseEvent; |
| using blink::WebMouseWheelEvent; |
| using blink::WebTouchEvent; |
| using blink::WebTouchPoint; |
| using ui::DidOverscrollParams; |
| |
| namespace content { |
| |
| namespace { |
| |
| int64_t GetEventLatencyMicros(double event_timestamp, base::TimeTicks now) { |
| return (now - base::TimeDelta::FromSecondsD(event_timestamp)) |
| .ToInternalValue(); |
| } |
| |
| void LogInputEventLatencyUma(const WebInputEvent& event, base::TimeTicks now) { |
| WebInputEvent::Type event_type = event.GetType(); |
| UMA_HISTOGRAM_CUSTOM_COUNTS( |
| "Event.AggregatedLatency.Renderer2", |
| GetEventLatencyMicros(event.TimeStampSeconds(), now), 1, 10000000, 100); |
| |
| #define CASE_TYPE(t) \ |
| case WebInputEvent::t: \ |
| UMA_HISTOGRAM_CUSTOM_COUNTS( \ |
| "Event.Latency.Renderer2." #t, \ |
| GetEventLatencyMicros(event.TimeStampSeconds(), now), 1, 10000000, \ |
| 100); \ |
| break; |
| |
| switch (event_type) { |
| CASE_TYPE(kUndefined); |
| CASE_TYPE(kMouseDown); |
| CASE_TYPE(kMouseUp); |
| CASE_TYPE(kMouseMove); |
| CASE_TYPE(kMouseEnter); |
| CASE_TYPE(kMouseLeave); |
| CASE_TYPE(kContextMenu); |
| CASE_TYPE(kMouseWheel); |
| CASE_TYPE(kRawKeyDown); |
| CASE_TYPE(kKeyDown); |
| CASE_TYPE(kKeyUp); |
| CASE_TYPE(kChar); |
| CASE_TYPE(kGestureScrollBegin); |
| CASE_TYPE(kGestureScrollEnd); |
| CASE_TYPE(kGestureScrollUpdate); |
| CASE_TYPE(kGestureFlingStart); |
| CASE_TYPE(kGestureFlingCancel); |
| CASE_TYPE(kGestureShowPress); |
| CASE_TYPE(kGestureTap); |
| CASE_TYPE(kGestureTapUnconfirmed); |
| CASE_TYPE(kGestureTapDown); |
| CASE_TYPE(kGestureTapCancel); |
| CASE_TYPE(kGestureDoubleTap); |
| CASE_TYPE(kGestureTwoFingerTap); |
| CASE_TYPE(kGestureLongPress); |
| CASE_TYPE(kGestureLongTap); |
| CASE_TYPE(kGesturePinchBegin); |
| CASE_TYPE(kGesturePinchEnd); |
| CASE_TYPE(kGesturePinchUpdate); |
| CASE_TYPE(kTouchStart); |
| CASE_TYPE(kTouchMove); |
| CASE_TYPE(kTouchEnd); |
| CASE_TYPE(kTouchCancel); |
| CASE_TYPE(kTouchScrollStarted); |
| default: |
| // Must include default to let blink::WebInputEvent add new event types |
| // before they're added here. |
| DLOG(WARNING) << "Unhandled WebInputEvent type: " << event_type; |
| break; |
| } |
| |
| #undef CASE_TYPE |
| } |
| |
| void LogPassiveEventListenersUma(WebInputEventResult result, |
| WebInputEvent::DispatchType dispatch_type, |
| double event_timestamp, |
| const ui::LatencyInfo& latency_info) { |
| enum { |
| PASSIVE_LISTENER_UMA_ENUM_PASSIVE, |
| PASSIVE_LISTENER_UMA_ENUM_UNCANCELABLE, |
| PASSIVE_LISTENER_UMA_ENUM_SUPPRESSED, |
| PASSIVE_LISTENER_UMA_ENUM_CANCELABLE, |
| PASSIVE_LISTENER_UMA_ENUM_CANCELABLE_AND_CANCELED, |
| PASSIVE_LISTENER_UMA_ENUM_FORCED_NON_BLOCKING_DUE_TO_FLING, |
| PASSIVE_LISTENER_UMA_ENUM_FORCED_NON_BLOCKING_DUE_TO_MAIN_THREAD_RESPONSIVENESS, |
| PASSIVE_LISTENER_UMA_ENUM_COUNT |
| }; |
| |
| int enum_value; |
| switch (dispatch_type) { |
| case WebInputEvent::kListenersForcedNonBlockingDueToFling: |
| enum_value = PASSIVE_LISTENER_UMA_ENUM_FORCED_NON_BLOCKING_DUE_TO_FLING; |
| break; |
| case WebInputEvent:: |
| kListenersForcedNonBlockingDueToMainThreadResponsiveness: |
| enum_value = |
| PASSIVE_LISTENER_UMA_ENUM_FORCED_NON_BLOCKING_DUE_TO_MAIN_THREAD_RESPONSIVENESS; |
| break; |
| case WebInputEvent::kListenersNonBlockingPassive: |
| enum_value = PASSIVE_LISTENER_UMA_ENUM_PASSIVE; |
| break; |
| case WebInputEvent::kEventNonBlocking: |
| enum_value = PASSIVE_LISTENER_UMA_ENUM_UNCANCELABLE; |
| break; |
| case WebInputEvent::kBlocking: |
| if (result == WebInputEventResult::kHandledApplication) |
| enum_value = PASSIVE_LISTENER_UMA_ENUM_CANCELABLE_AND_CANCELED; |
| else if (result == WebInputEventResult::kHandledSuppressed) |
| enum_value = PASSIVE_LISTENER_UMA_ENUM_SUPPRESSED; |
| else |
| enum_value = PASSIVE_LISTENER_UMA_ENUM_CANCELABLE; |
| break; |
| default: |
| NOTREACHED(); |
| return; |
| } |
| |
| UMA_HISTOGRAM_ENUMERATION("Event.PassiveListeners", enum_value, |
| PASSIVE_LISTENER_UMA_ENUM_COUNT); |
| |
| if (base::TimeTicks::IsHighResolution()) { |
| if (enum_value == PASSIVE_LISTENER_UMA_ENUM_CANCELABLE) { |
| base::TimeTicks now = base::TimeTicks::Now(); |
| UMA_HISTOGRAM_CUSTOM_COUNTS("Event.PassiveListeners.Latency", |
| GetEventLatencyMicros(event_timestamp, now), |
| 1, 10000000, 100); |
| } else if (enum_value == |
| PASSIVE_LISTENER_UMA_ENUM_FORCED_NON_BLOCKING_DUE_TO_FLING) { |
| base::TimeTicks now = base::TimeTicks::Now(); |
| UMA_HISTOGRAM_CUSTOM_COUNTS( |
| "Event.PassiveListeners.ForcedNonBlockingLatencyDueToFling", |
| GetEventLatencyMicros(event_timestamp, now), 1, 10000000, 100); |
| } else if ( |
| enum_value == |
| PASSIVE_LISTENER_UMA_ENUM_FORCED_NON_BLOCKING_DUE_TO_MAIN_THREAD_RESPONSIVENESS) { |
| base::TimeTicks now = base::TimeTicks::Now(); |
| UMA_HISTOGRAM_CUSTOM_COUNTS( |
| "Event.PassiveListeners." |
| "ForcedNonBlockingLatencyDueToUnresponsiveMainThread", |
| GetEventLatencyMicros(event_timestamp, now), 1, 10000000, 50); |
| } |
| } |
| } |
| |
| } // namespace |
| |
| RenderWidgetInputHandler::RenderWidgetInputHandler( |
| RenderWidgetInputHandlerDelegate* delegate, |
| RenderWidget* widget) |
| : delegate_(delegate), |
| widget_(widget), |
| handling_input_event_(false), |
| handling_event_overscroll_(nullptr), |
| handling_event_type_(WebInputEvent::kUndefined), |
| context_menu_source_type_(ui::MENU_SOURCE_MOUSE), |
| suppress_next_char_events_(false) { |
| DCHECK(delegate); |
| DCHECK(widget); |
| delegate->SetInputHandler(this); |
| } |
| |
| RenderWidgetInputHandler::~RenderWidgetInputHandler() {} |
| |
| InputEventAckState RenderWidgetInputHandler::HandleInputEvent( |
| const blink::WebCoalescedInputEvent& coalesced_event, |
| const ui::LatencyInfo& latency_info, |
| InputEventDispatchType dispatch_type) { |
| const WebInputEvent& input_event = coalesced_event.Event(); |
| base::AutoReset<bool> handling_input_event_resetter(&handling_input_event_, |
| true); |
| base::AutoReset<WebInputEvent::Type> handling_event_type_resetter( |
| &handling_event_type_, input_event.GetType()); |
| |
| // Calls into |didOverscroll()| while handling this event will populate |
| // |event_overscroll|, which in turn will be bundled with the event ack. |
| std::unique_ptr<DidOverscrollParams> event_overscroll; |
| base::AutoReset<std::unique_ptr<DidOverscrollParams>*> |
| handling_event_overscroll_resetter(&handling_event_overscroll_, |
| &event_overscroll); |
| |
| #if defined(OS_ANDROID) |
| ImeEventGuard guard(widget_); |
| #endif |
| |
| base::TimeTicks start_time; |
| if (base::TimeTicks::IsHighResolution()) |
| start_time = base::TimeTicks::Now(); |
| |
| TRACE_EVENT1("renderer,benchmark,rail", |
| "RenderWidgetInputHandler::OnHandleInputEvent", "event", |
| WebInputEvent::GetName(input_event.GetType())); |
| TRACE_EVENT_SYNTHETIC_DELAY_BEGIN("blink.HandleInputEvent"); |
| TRACE_EVENT_WITH_FLOW1("input,benchmark", "LatencyInfo.Flow", |
| TRACE_ID_DONT_MANGLE(latency_info.trace_id()), |
| TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, |
| "step", "HandleInputEventMain"); |
| |
| // If we don't have a high res timer, these metrics won't be accurate enough |
| // to be worth collecting. Note that this does introduce some sampling bias. |
| if (!start_time.is_null()) |
| LogInputEventLatencyUma(input_event, start_time); |
| |
| std::unique_ptr<cc::SwapPromiseMonitor> latency_info_swap_promise_monitor; |
| ui::LatencyInfo swap_latency_info(latency_info); |
| swap_latency_info.AddLatencyNumber( |
| ui::LatencyComponentType::INPUT_EVENT_LATENCY_RENDERER_MAIN_COMPONENT, 0, |
| 0); |
| if (widget_->compositor()) { |
| latency_info_swap_promise_monitor = |
| widget_->compositor()->CreateLatencyInfoSwapPromiseMonitor( |
| &swap_latency_info); |
| } |
| |
| bool prevent_default = false; |
| if (WebInputEvent::IsMouseEventType(input_event.GetType())) { |
| const WebMouseEvent& mouse_event = |
| static_cast<const WebMouseEvent&>(input_event); |
| TRACE_EVENT2("renderer", "HandleMouseMove", "x", |
| mouse_event.PositionInWidget().x, "y", |
| mouse_event.PositionInWidget().y); |
| context_menu_source_type_ = ui::MENU_SOURCE_MOUSE; |
| prevent_default = delegate_->WillHandleMouseEvent(mouse_event); |
| } |
| |
| if (WebInputEvent::IsKeyboardEventType(input_event.GetType())) { |
| context_menu_source_type_ = ui::MENU_SOURCE_KEYBOARD; |
| #if defined(OS_ANDROID) |
| // The DPAD_CENTER key on Android has a dual semantic: (1) in the general |
| // case it should behave like a select key (i.e. causing a click if a button |
| // is focused). However, if a text field is focused (2), its intended |
| // behavior is to just show the IME and don't propagate the key. |
| // A typical use case is a web form: the DPAD_CENTER should bring up the IME |
| // when clicked on an input text field and cause the form submit if clicked |
| // when the submit button is focused, but not vice-versa. |
| // The UI layer takes care of translating DPAD_CENTER into a RETURN key, |
| // but at this point we have to swallow the event for the scenario (2). |
| const WebKeyboardEvent& key_event = |
| static_cast<const WebKeyboardEvent&>(input_event); |
| if (key_event.native_key_code == AKEYCODE_DPAD_CENTER && |
| widget_->GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE) { |
| widget_->ShowVirtualKeyboardOnElementFocus(); |
| prevent_default = true; |
| } |
| #endif |
| } |
| |
| if (WebInputEvent::IsGestureEventType(input_event.GetType())) { |
| const WebGestureEvent& gesture_event = |
| static_cast<const WebGestureEvent&>(input_event); |
| if (input_event.GetType() == WebInputEvent::kGestureLongPress) { |
| context_menu_source_type_ = ui::MENU_SOURCE_LONG_PRESS; |
| } else if (input_event.GetType() == WebInputEvent::kGestureLongTap) { |
| context_menu_source_type_ = ui::MENU_SOURCE_LONG_TAP; |
| } else { |
| context_menu_source_type_ = ui::MENU_SOURCE_TOUCH; |
| } |
| prevent_default = |
| prevent_default || delegate_->WillHandleGestureEvent(gesture_event); |
| } |
| |
| WebInputEventResult processed = prevent_default |
| ? WebInputEventResult::kHandledSuppressed |
| : WebInputEventResult::kNotHandled; |
| if (input_event.GetType() != WebInputEvent::kChar || |
| !suppress_next_char_events_) { |
| suppress_next_char_events_ = false; |
| if (processed == WebInputEventResult::kNotHandled && |
| widget_->GetWebWidget()) |
| processed = widget_->GetWebWidget()->HandleInputEvent(coalesced_event); |
| } |
| |
| // TODO(dtapuska): Use the input_event.timeStampSeconds as the start |
| // ideally this should be when the event was sent by the compositor to the |
| // renderer. crbug.com/565348 |
| if (input_event.GetType() == WebInputEvent::kTouchStart || |
| input_event.GetType() == WebInputEvent::kTouchMove || |
| input_event.GetType() == WebInputEvent::kTouchEnd) { |
| const WebTouchEvent& touch = static_cast<const WebTouchEvent&>(input_event); |
| |
| LogPassiveEventListenersUma(processed, touch.dispatch_type, |
| input_event.TimeStampSeconds(), latency_info); |
| |
| // TODO(lanwei): Remove this metric for event latency outside fling in M56, |
| // once we've gathered enough data to decide if we want to ship the passive |
| // event listener for fling, see https://crbug.com/638661. |
| if (touch.dispatch_type == WebInputEvent::kBlocking && |
| touch.touch_start_or_first_touch_move && |
| base::TimeTicks::IsHighResolution()) { |
| base::TimeTicks now = base::TimeTicks::Now(); |
| UMA_HISTOGRAM_CUSTOM_COUNTS( |
| "Event.Touch.TouchLatencyOutsideFling", |
| GetEventLatencyMicros(input_event.TimeStampSeconds(), now), 1, |
| 100000000, 50); |
| } |
| } else if (input_event.GetType() == WebInputEvent::kMouseWheel) { |
| LogPassiveEventListenersUma( |
| processed, |
| static_cast<const WebMouseWheelEvent&>(input_event).dispatch_type, |
| input_event.TimeStampSeconds(), latency_info); |
| } |
| |
| // If this RawKeyDown event corresponds to a browser keyboard shortcut and |
| // it's not processed by webkit, then we need to suppress the upcoming Char |
| // events. |
| bool is_keyboard_shortcut = |
| input_event.GetType() == WebInputEvent::kRawKeyDown && |
| static_cast<const WebKeyboardEvent&>(input_event).is_browser_shortcut; |
| if (processed == WebInputEventResult::kNotHandled && is_keyboard_shortcut) |
| suppress_next_char_events_ = true; |
| |
| InputEventAckState ack_result = processed == WebInputEventResult::kNotHandled |
| ? INPUT_EVENT_ACK_STATE_NOT_CONSUMED |
| : INPUT_EVENT_ACK_STATE_CONSUMED; |
| if (processed == WebInputEventResult::kNotHandled && |
| input_event.GetType() == WebInputEvent::kTouchStart) { |
| const WebTouchEvent& touch_event = |
| static_cast<const WebTouchEvent&>(input_event); |
| // Hit-test for all the pressed touch points. If there is a touch-handler |
| // for any of the touch points, then the renderer should continue to receive |
| // touch events. |
| ack_result = INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS; |
| for (size_t i = 0; i < touch_event.touches_length; ++i) { |
| if (touch_event.touches[i].state == WebTouchPoint::kStatePressed && |
| delegate_->HasTouchEventHandlersAt( |
| gfx::ToFlooredPoint(touch_event.touches[i].position))) { |
| ack_result = INPUT_EVENT_ACK_STATE_NOT_CONSUMED; |
| break; |
| } |
| } |
| } |
| |
| // Send gesture scroll events and their dispositions to the compositor thread, |
| // so that they can be used to produce the elastic overscroll effect on Mac. |
| if (input_event.GetType() == WebInputEvent::kGestureScrollBegin || |
| input_event.GetType() == WebInputEvent::kGestureScrollEnd || |
| input_event.GetType() == WebInputEvent::kGestureScrollUpdate) { |
| const WebGestureEvent& gesture_event = |
| static_cast<const WebGestureEvent&>(input_event); |
| if (gesture_event.source_device == blink::kWebGestureDeviceTouchpad) { |
| delegate_->ObserveGestureEventAndResult( |
| gesture_event, |
| event_overscroll ? event_overscroll->latest_overscroll_delta |
| : gfx::Vector2dF(), |
| processed != WebInputEventResult::kNotHandled); |
| } |
| } |
| |
| TRACE_EVENT_SYNTHETIC_DELAY_END("blink.HandleInputEvent"); |
| |
| if (dispatch_type == DISPATCH_TYPE_BLOCKING) { |
| std::unique_ptr<InputEventAck> response(new InputEventAck( |
| InputEventAckSource::MAIN_THREAD, input_event.GetType(), ack_result, |
| swap_latency_info, std::move(event_overscroll), |
| ui::WebInputEventTraits::GetUniqueTouchEventId(input_event))); |
| delegate_->OnInputEventAck(std::move(response)); |
| } else { |
| DCHECK(!event_overscroll) << "Unexpected overscroll for un-acked event"; |
| } |
| if (RenderThreadImpl::current()) { |
| RenderThreadImpl::current() |
| ->GetRendererScheduler() |
| ->DidHandleInputEventOnMainThread(input_event, processed); |
| } |
| |
| #if defined(OS_ANDROID) |
| // Allow the IME to be shown when the focus changes as a consequence |
| // of a processed touch end event. |
| if (input_event.GetType() == WebInputEvent::kTouchEnd && |
| processed != WebInputEventResult::kNotHandled) { |
| delegate_->ShowVirtualKeyboard(); |
| } |
| #elif defined(USE_AURA) |
| // Show the virtual keyboard if enabled and a user gesture triggers a focus |
| // change. |
| if (processed != WebInputEventResult::kNotHandled && |
| (input_event.GetType() == WebInputEvent::kTouchEnd || |
| input_event.GetType() == WebInputEvent::kMouseUp)) { |
| delegate_->ShowVirtualKeyboard(); |
| } |
| #endif |
| |
| if (!prevent_default && |
| WebInputEvent::IsKeyboardEventType(input_event.GetType())) |
| delegate_->OnDidHandleKeyEvent(); |
| |
| // TODO(rouslan): Fix ChromeOS and Windows 8 behavior of autofill popup with |
| // virtual keyboard. |
| #if !defined(OS_ANDROID) |
| // Virtual keyboard is not supported, so react to focus change immediately. |
| if (processed != WebInputEventResult::kNotHandled && |
| (input_event.GetType() == WebInputEvent::kTouchEnd || |
| input_event.GetType() == WebInputEvent::kMouseUp)) { |
| delegate_->FocusChangeComplete(); |
| } |
| #endif |
| return ack_result; |
| } |
| |
| void RenderWidgetInputHandler::DidOverscrollFromBlink( |
| const WebFloatSize& overscrollDelta, |
| const WebFloatSize& accumulatedOverscroll, |
| const WebFloatPoint& position, |
| const WebFloatSize& velocity) { |
| std::unique_ptr<DidOverscrollParams> params(new DidOverscrollParams()); |
| params->accumulated_overscroll = gfx::Vector2dF( |
| accumulatedOverscroll.width, accumulatedOverscroll.height); |
| params->latest_overscroll_delta = |
| gfx::Vector2dF(overscrollDelta.width, overscrollDelta.height); |
| params->current_fling_velocity = |
| gfx::Vector2dF(velocity.width, velocity.height); |
| params->causal_event_viewport_point = gfx::PointF(position.x, position.y); |
| |
| // If we're currently handling an event, stash the overscroll data such that |
| // it can be bundled in the event ack. |
| if (handling_event_overscroll_) { |
| *handling_event_overscroll_ = std::move(params); |
| return; |
| } |
| |
| delegate_->OnDidOverscroll(*params); |
| } |
| |
| } // namespace content |