blob: 368619cc1e633721beac4f37fa29314ea22bce62 [file] [log] [blame]
// 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<IPC::Message> 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<int>(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<MouseEventWithLatencyInfo> 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<RenderWidgetHost> 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<void>(this),
Details<int>(&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