// Copyright 2013 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/input/immediate_input_router.h" #include "base/command_line.h" #include "base/metrics/histogram.h" #include "content/browser/renderer_host/input/gesture_event_filter.h" #include "content/browser/renderer_host/input/input_router_client.h" #include "content/browser/renderer_host/input/touch_event_queue.h" #include "content/browser/renderer_host/input/touchpad_tap_suppression_controller.h" #include "content/browser/renderer_host/render_process_host_impl.h" #include "content/common/content_constants_internal.h" #include "content/common/edit_command.h" #include "content/common/input_messages.h" #include "content/common/view_messages.h" #include "content/port/common/input_event_ack_state.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_types.h" #include "content/public/browser/user_metrics.h" #include "content/public/common/content_switches.h" #include "ui/base/events/event.h" #include "ui/base/keycodes/keyboard_codes.h" using base::Time; using base::TimeDelta; using base::TimeTicks; using WebKit::WebGestureEvent; using WebKit::WebInputEvent; using WebKit::WebKeyboardEvent; using WebKit::WebMouseEvent; using WebKit::WebMouseWheelEvent; namespace content { namespace { // Returns |true| if the two wheel events should be coalesced. bool ShouldCoalesceMouseWheelEvents(const WebMouseWheelEvent& last_event, const WebMouseWheelEvent& new_event) { return last_event.modifiers == new_event.modifiers && last_event.scrollByPage == new_event.scrollByPage && last_event.hasPreciseScrollingDeltas == new_event.hasPreciseScrollingDeltas && last_event.phase == new_event.phase && last_event.momentumPhase == new_event.momentumPhase; } float GetUnacceleratedDelta(float accelerated_delta, float acceleration_ratio) { return accelerated_delta * acceleration_ratio; } float GetAccelerationRatio(float accelerated_delta, float unaccelerated_delta) { if (unaccelerated_delta == 0.f || accelerated_delta == 0.f) return 1.f; return unaccelerated_delta / accelerated_delta; } const char* GetEventAckName(InputEventAckState ack_result) { switch(ack_result) { case INPUT_EVENT_ACK_STATE_UNKNOWN: return "UNKNOWN"; case INPUT_EVENT_ACK_STATE_CONSUMED: return "CONSUMED"; case INPUT_EVENT_ACK_STATE_NOT_CONSUMED: return "NOT_CONSUMED"; case INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS: return "NO_CONSUMER_EXISTS"; default: DLOG(WARNING) << "Unhandled InputEventAckState in GetEventAckName.\n"; break; } return ""; } } // namespace ImmediateInputRouter::ImmediateInputRouter( RenderProcessHost* process, InputRouterClient* client, int routing_id) : process_(process), client_(client), routing_id_(routing_id), select_range_pending_(false), move_caret_pending_(false), mouse_move_pending_(false), mouse_wheel_pending_(false), has_touch_handler_(false), touch_event_queue_(new TouchEventQueue(this)), gesture_event_filter_(new GestureEventFilter(this)) { enable_no_touch_to_renderer_while_scrolling_ = CommandLine::ForCurrentProcess()->GetSwitchValueASCII( switches::kNoTouchToRendererWhileScrolling) == "1"; DCHECK(process); DCHECK(client); } ImmediateInputRouter::~ImmediateInputRouter() { } bool ImmediateInputRouter::SendInput(IPC::Message* message) { DCHECK(IPC_MESSAGE_ID_CLASS(message->type()) == InputMsgStart); scoped_ptr scoped_message(message); switch (scoped_message->type()) { // Check for types that require an ACK. case InputMsg_SelectRange::ID: return SendSelectRange(scoped_message.release()); case InputMsg_MoveCaret::ID: return SendMoveCaret(scoped_message.release()); case InputMsg_HandleInputEvent::ID: NOTREACHED() << "WebInputEvents should never be sent via SendInput."; return false; default: return Send(scoped_message.release()); } } void ImmediateInputRouter::SendMouseEvent( const MouseEventWithLatencyInfo& mouse_event) { if (!client_->OnSendMouseEvent(mouse_event)) return; if (mouse_event.event.type == WebInputEvent::MouseDown && gesture_event_filter_->GetTouchpadTapSuppressionController()-> ShouldDeferMouseDown(mouse_event)) return; if (mouse_event.event.type == WebInputEvent::MouseUp && gesture_event_filter_->GetTouchpadTapSuppressionController()-> ShouldSuppressMouseUp()) return; SendMouseEventImmediately(mouse_event); } void ImmediateInputRouter::SendWheelEvent( const MouseWheelEventWithLatencyInfo& wheel_event) { if (!client_->OnSendWheelEvent(wheel_event)) return; // If there's already a mouse wheel event waiting to be sent to the renderer, // add the new deltas to that event. Not doing so (e.g., by dropping the old // event, as for mouse moves) results in very slow scrolling on the Mac (on // which many, very small wheel events are sent). if (mouse_wheel_pending_) { if (coalesced_mouse_wheel_events_.empty() || !ShouldCoalesceMouseWheelEvents( coalesced_mouse_wheel_events_.back().event, wheel_event.event)) { coalesced_mouse_wheel_events_.push_back(wheel_event); } else { MouseWheelEventWithLatencyInfo* last_wheel_event = &coalesced_mouse_wheel_events_.back(); float unaccelerated_x = GetUnacceleratedDelta(last_wheel_event->event.deltaX, last_wheel_event->event.accelerationRatioX) + GetUnacceleratedDelta(wheel_event.event.deltaX, wheel_event.event.accelerationRatioX); float unaccelerated_y = GetUnacceleratedDelta(last_wheel_event->event.deltaY, last_wheel_event->event.accelerationRatioY) + GetUnacceleratedDelta(wheel_event.event.deltaY, wheel_event.event.accelerationRatioY); last_wheel_event->event.deltaX += wheel_event.event.deltaX; last_wheel_event->event.deltaY += wheel_event.event.deltaY; last_wheel_event->event.wheelTicksX += wheel_event.event.wheelTicksX; last_wheel_event->event.wheelTicksY += wheel_event.event.wheelTicksY; last_wheel_event->event.accelerationRatioX = GetAccelerationRatio(last_wheel_event->event.deltaX, unaccelerated_x); last_wheel_event->event.accelerationRatioY = GetAccelerationRatio(last_wheel_event->event.deltaY, unaccelerated_y); DCHECK_GE(wheel_event.event.timeStampSeconds, last_wheel_event->event.timeStampSeconds); last_wheel_event->event.timeStampSeconds = wheel_event.event.timeStampSeconds; last_wheel_event->latency.MergeWith(wheel_event.latency); } return; } mouse_wheel_pending_ = true; current_wheel_event_ = wheel_event; HISTOGRAM_COUNTS_100("Renderer.WheelQueueSize", coalesced_mouse_wheel_events_.size()); FilterAndSendWebInputEvent(wheel_event.event, wheel_event.latency, false); } void ImmediateInputRouter::SendKeyboardEvent( const NativeWebKeyboardEvent& key_event, const ui::LatencyInfo& latency_info) { bool is_shortcut = false; if (!client_->OnSendKeyboardEvent(key_event, latency_info, &is_shortcut)) return; // Put all WebKeyboardEvent objects in a queue since we can't trust the // renderer and we need to give something to the HandleKeyboardEvent // handler. key_queue_.push_back(key_event); HISTOGRAM_COUNTS_100("Renderer.KeyboardQueueSize", key_queue_.size()); gesture_event_filter_->FlingHasBeenHalted(); // Only forward the non-native portions of our event. FilterAndSendWebInputEvent(key_event, latency_info, is_shortcut); } void ImmediateInputRouter::SendGestureEvent( const GestureEventWithLatencyInfo& gesture_event) { HandleGestureScroll(gesture_event); if (!client_->OnSendGestureEvent(gesture_event)) return; FilterAndSendWebInputEvent(gesture_event.event, gesture_event.latency, false); } void ImmediateInputRouter::SendTouchEvent( const TouchEventWithLatencyInfo& touch_event) { // Always queue TouchEvents, even if the client request they be dropped. client_->OnSendTouchEvent(touch_event); touch_event_queue_->QueueEvent(touch_event); } // Forwards MouseEvent without passing it through // TouchpadTapSuppressionController. void ImmediateInputRouter::SendMouseEventImmediately( const MouseEventWithLatencyInfo& mouse_event) { if (!client_->OnSendMouseEventImmediately(mouse_event)) return; // Avoid spamming the renderer with mouse move events. It is important // to note that WM_MOUSEMOVE events are anyways synthetic, but since our // thread is able to rapidly consume WM_MOUSEMOVE events, we may get way // more WM_MOUSEMOVE events than we wish to send to the renderer. if (mouse_event.event.type == WebInputEvent::MouseMove) { if (mouse_move_pending_) { if (!next_mouse_move_) { next_mouse_move_.reset(new MouseEventWithLatencyInfo(mouse_event)); } else { // Accumulate movement deltas. int x = next_mouse_move_->event.movementX; int y = next_mouse_move_->event.movementY; next_mouse_move_->event = mouse_event.event; next_mouse_move_->event.movementX += x; next_mouse_move_->event.movementY += y; next_mouse_move_->latency.MergeWith(mouse_event.latency); } return; } mouse_move_pending_ = true; } FilterAndSendWebInputEvent(mouse_event.event, mouse_event.latency, false); } void ImmediateInputRouter::SendTouchEventImmediately( const TouchEventWithLatencyInfo& touch_event) { if (!client_->OnSendTouchEventImmediately(touch_event)) return; FilterAndSendWebInputEvent(touch_event.event, touch_event.latency, false); } void ImmediateInputRouter::SendGestureEventImmediately( const GestureEventWithLatencyInfo& gesture_event) { HandleGestureScroll(gesture_event); if (!client_->OnSendGestureEventImmediately(gesture_event)) return; FilterAndSendWebInputEvent(gesture_event.event, gesture_event.latency, false); } const NativeWebKeyboardEvent* ImmediateInputRouter::GetLastKeyboardEvent() const { if (key_queue_.empty()) return NULL; return &key_queue_.front(); } bool ImmediateInputRouter::ShouldForwardTouchEvent() const { // Always send a touch event if the renderer has a touch-event handler. It is // possible that a renderer stops listening to touch-events while there are // still events in the touch-queue. In such cases, the new events should still // get into the queue. return has_touch_handler_ || !touch_event_queue_->empty(); } bool ImmediateInputRouter::ShouldForwardGestureEvent( const GestureEventWithLatencyInfo& touch_event) const { return gesture_event_filter_->ShouldForward(touch_event); } bool ImmediateInputRouter::HasQueuedGestureEvents() const { return gesture_event_filter_->HasQueuedGestureEvents(); } bool ImmediateInputRouter::OnMessageReceived(const IPC::Message& message) { bool handled = true; bool message_is_ok = true; IPC_BEGIN_MESSAGE_MAP_EX(ImmediateInputRouter, message, message_is_ok) IPC_MESSAGE_HANDLER(InputHostMsg_HandleInputEvent_ACK, OnInputEventAck) IPC_MESSAGE_HANDLER(ViewHostMsg_MoveCaret_ACK, OnMsgMoveCaretAck) IPC_MESSAGE_HANDLER(ViewHostMsg_SelectRange_ACK, OnSelectRangeAck) IPC_MESSAGE_HANDLER(ViewHostMsg_HasTouchEventHandlers, OnHasTouchEventHandlers) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() if (!message_is_ok) client_->OnUnexpectedEventAck(true); return handled; } void ImmediateInputRouter::OnTouchEventAck( const TouchEventWithLatencyInfo& event, InputEventAckState ack_result) { client_->OnTouchEventAck(event, ack_result); } bool ImmediateInputRouter::SendSelectRange(IPC::Message* message) { DCHECK(message->type() == InputMsg_SelectRange::ID); if (select_range_pending_) { next_selection_range_.reset(message); return true; } select_range_pending_ = true; return Send(message); } bool ImmediateInputRouter::SendMoveCaret(IPC::Message* message) { DCHECK(message->type() == InputMsg_MoveCaret::ID); if (move_caret_pending_) { next_move_caret_.reset(message); return true; } move_caret_pending_ = true; return Send(message); } bool ImmediateInputRouter::Send(IPC::Message* message) { return process_->Send(message); } void ImmediateInputRouter::SendWebInputEvent( const WebInputEvent& input_event, const ui::LatencyInfo& latency_info, bool is_keyboard_shortcut) { input_event_start_time_ = TimeTicks::Now(); Send(new InputMsg_HandleInputEvent( routing_id(), &input_event, latency_info, is_keyboard_shortcut)); client_->IncrementInFlightEventCount(); } void ImmediateInputRouter::FilterAndSendWebInputEvent( const WebInputEvent& input_event, const ui::LatencyInfo& latency_info, bool is_keyboard_shortcut) { TRACE_EVENT0("input", "ImmediateInputRouter::FilterAndSendWebInputEvent"); if (!process_->HasConnection()) return; DCHECK(!process_->IgnoreInputEvents()); // Perform optional, synchronous event handling, sending ACK messages for // processed events, or proceeding as usual. InputEventAckState filter_ack = client_->FilterInputEvent(input_event, latency_info); switch (filter_ack) { // Send the ACK and early exit. case INPUT_EVENT_ACK_STATE_CONSUMED: case INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS: next_mouse_move_.reset(); ProcessInputEventAck(input_event.type, filter_ack, latency_info); // WARNING: |this| may be deleted at this point. return; case INPUT_EVENT_ACK_STATE_UNKNOWN: { if (input_event.type == WebKit::WebInputEvent::MouseMove) { // Since this mouse-move event has been consumed, there will be no ACKs. // So reset the state here so that future mouse-move events do reach the // renderer. mouse_move_pending_ = false; } else if (input_event.type == WebKit::WebInputEvent::MouseWheel) { // Reset the wheel-event state when appropriate. mouse_wheel_pending_ = false; } else if (WebInputEvent::isGestureEventType(input_event.type) && gesture_event_filter_->HasQueuedGestureEvents()) { // If the gesture-event filter has queued gesture events, that implies // it's awaiting an ack for the event. Since the event is being dropped, // it is never sent to the renderer, and so it won't receive any ACKs. // So send the ACK to the gesture event filter immediately, and mark it // as having been processed. gesture_event_filter_->ProcessGestureAck(true, input_event.type); } else if (WebInputEvent::isTouchEventType(input_event.type)) { // During an overscroll gesture initiated by touch-scrolling, the // touch-events do not reset or contribute to the overscroll gesture. // However, the touch-events are not sent to the renderer. So send an // ACK to the touch-event queue immediately. Mark the event as not // processed, to make sure that the touch-scroll gesture that initiated // the overscroll is updated properly. touch_event_queue_->ProcessTouchAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED, latency_info); } return; } // Proceed as normal. case INPUT_EVENT_ACK_STATE_NOT_CONSUMED: break; }; // Transmit any pending wheel events on a non-wheel event. This ensures that // the renderer receives the final PhaseEnded wheel event, which is necessary // to terminate rubber-banding, for example. if (input_event.type != WebInputEvent::MouseWheel) { for (size_t i = 0; i < coalesced_mouse_wheel_events_.size(); ++i) { SendWebInputEvent(coalesced_mouse_wheel_events_[i].event, coalesced_mouse_wheel_events_[i].latency, false); } coalesced_mouse_wheel_events_.clear(); } SendWebInputEvent(input_event, latency_info, is_keyboard_shortcut); // Any input event cancels a pending mouse move event. next_mouse_move_.reset(); } void ImmediateInputRouter::OnInputEventAck( WebInputEvent::Type event_type, InputEventAckState ack_result, const ui::LatencyInfo& latency_info) { // Log the time delta for processing an input event. TimeDelta delta = TimeTicks::Now() - input_event_start_time_; UMA_HISTOGRAM_TIMES("MPArch.IIR_InputEventDelta", delta); client_->DecrementInFlightEventCount(); ProcessInputEventAck(event_type, ack_result, latency_info); } void ImmediateInputRouter::OnMsgMoveCaretAck() { move_caret_pending_ = false; if (next_move_caret_) SendMoveCaret(next_move_caret_.release()); } void ImmediateInputRouter::OnSelectRangeAck() { select_range_pending_ = false; if (next_selection_range_) SendSelectRange(next_selection_range_.release()); } void ImmediateInputRouter::OnHasTouchEventHandlers(bool has_handlers) { if (has_touch_handler_ == has_handlers) return; has_touch_handler_ = has_handlers; if (!has_handlers) touch_event_queue_->FlushQueue(); client_->OnHasTouchEventHandlers(has_handlers); } void ImmediateInputRouter::ProcessInputEventAck( WebInputEvent::Type event_type, InputEventAckState ack_result, const ui::LatencyInfo& latency_info) { TRACE_EVENT1("input", "ImmediateInputRouter::ProcessInputEventAck", "ack", GetEventAckName(ack_result)); int type = static_cast(event_type); if (type < WebInputEvent::Undefined) { client_->OnUnexpectedEventAck(true); } else if (type == WebInputEvent::MouseMove) { mouse_move_pending_ = false; // now, we can send the next mouse move event if (next_mouse_move_) { DCHECK(next_mouse_move_->event.type == WebInputEvent::MouseMove); scoped_ptr next_mouse_move = next_mouse_move_.Pass(); SendMouseEvent(*next_mouse_move); } } else if (WebInputEvent::isKeyboardEventType(type)) { ProcessKeyboardAck(type, ack_result); } else if (type == WebInputEvent::MouseWheel) { ProcessWheelAck(ack_result); } else if (WebInputEvent::isTouchEventType(type)) { ProcessTouchAck(ack_result, latency_info); } else if (WebInputEvent::isGestureEventType(type)) { ProcessGestureAck(type, ack_result); } // WARNING: |this| may be deleted at this point. // This is used only for testing, and the other end does not use the // source object. On linux, specifying // Source results in a very strange // runtime error in the epilogue of the enclosing // (ProcessInputEventAck) method, but not on other platforms; using // 'void' instead is just as safe (since NotificationSource // is not actually typesafe) and avoids this error. NotificationService::current()->Notify( NOTIFICATION_RENDER_WIDGET_HOST_DID_RECEIVE_INPUT_EVENT_ACK, Source(this), Details(&type)); } void ImmediateInputRouter::ProcessKeyboardAck( int type, InputEventAckState ack_result) { if (key_queue_.empty()) { LOG(ERROR) << "Got a KeyEvent back from the renderer but we " << "don't seem to have sent it to the renderer!"; } else if (key_queue_.front().type != type) { LOG(ERROR) << "We seem to have a different key type sent from " << "the renderer. (" << key_queue_.front().type << " vs. " << type << "). Ignoring event."; // Something must be wrong. Clear the |key_queue_| and char event // suppression so that we can resume from the error. key_queue_.clear(); client_->OnUnexpectedEventAck(false); } else { NativeWebKeyboardEvent front_item = key_queue_.front(); key_queue_.pop_front(); client_->OnKeyboardEventAck(front_item, ack_result); // WARNING: This ImmediateInputRouter can be deallocated at this point // (i.e. in the case of Ctrl+W, where the call to // HandleKeyboardEvent destroys this ImmediateInputRouter). } } void ImmediateInputRouter::ProcessWheelAck(InputEventAckState ack_result) { mouse_wheel_pending_ = false; // Process the unhandled wheel event here before calling // ForwardWheelEventWithLatencyInfo() since it will mutate // current_wheel_event_. client_->OnWheelEventAck(current_wheel_event_.event, ack_result); // Now send the next (coalesced) mouse wheel event. if (!coalesced_mouse_wheel_events_.empty()) { MouseWheelEventWithLatencyInfo next_wheel_event = coalesced_mouse_wheel_events_.front(); coalesced_mouse_wheel_events_.pop_front(); SendWheelEvent(next_wheel_event); } } void ImmediateInputRouter::ProcessGestureAck(int type, InputEventAckState ack_result) { const bool processed = (INPUT_EVENT_ACK_STATE_CONSUMED == ack_result); client_->OnGestureEventAck( gesture_event_filter_->GetGestureEventAwaitingAck(), ack_result); gesture_event_filter_->ProcessGestureAck(processed, type); } void ImmediateInputRouter::ProcessTouchAck( InputEventAckState ack_result, const ui::LatencyInfo& latency_info) { // |touch_event_queue_| will forward to OnTouchEventAck when appropriate. touch_event_queue_->ProcessTouchAck(ack_result, latency_info); } void ImmediateInputRouter::HandleGestureScroll( const GestureEventWithLatencyInfo& gesture_event) { if (!enable_no_touch_to_renderer_while_scrolling_) return; // Once scrolling is started stop forwarding touch move events to renderer. if (gesture_event.event.type == WebInputEvent::GestureScrollBegin) touch_event_queue_->set_no_touch_move_to_renderer(true); if (gesture_event.event.type == WebInputEvent::GestureScrollEnd || gesture_event.event.type == WebInputEvent::GestureFlingStart) { touch_event_queue_->set_no_touch_move_to_renderer(false); } } } // namespace content