blob: 95f0fc7721aaabc2383436516bfe1f9722bc06d9 [file] [log] [blame]
// Copyright 2016 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/mouse_wheel_event_queue.h"
#include "base/metrics/histogram_macros.h"
#include "base/stl_util.h"
#include "base/trace_event/trace_event.h"
using blink::WebGestureEvent;
using blink::WebInputEvent;
using blink::WebMouseWheelEvent;
using ui::LatencyInfo;
namespace content {
// This class represents a single queued mouse wheel event. Its main use
// is that it is reported via trace events.
class QueuedWebMouseWheelEvent : public MouseWheelEventWithLatencyInfo {
public:
QueuedWebMouseWheelEvent(const MouseWheelEventWithLatencyInfo& original_event)
: MouseWheelEventWithLatencyInfo(original_event) {
TRACE_EVENT_ASYNC_BEGIN0("input", "MouseWheelEventQueue::QueueEvent", this);
}
~QueuedWebMouseWheelEvent() {
TRACE_EVENT_ASYNC_END0("input", "MouseWheelEventQueue::QueueEvent", this);
}
private:
DISALLOW_COPY_AND_ASSIGN(QueuedWebMouseWheelEvent);
};
MouseWheelEventQueue::MouseWheelEventQueue(MouseWheelEventQueueClient* client,
int64_t scroll_transaction_ms)
: client_(client),
needs_scroll_begin_(true),
needs_scroll_end_(false),
scroll_transaction_ms_(scroll_transaction_ms),
scrolling_device_(blink::WebGestureDeviceUninitialized) {
DCHECK(client);
}
MouseWheelEventQueue::~MouseWheelEventQueue() {
if (!wheel_queue_.empty())
STLDeleteElements(&wheel_queue_);
}
void MouseWheelEventQueue::QueueEvent(
const MouseWheelEventWithLatencyInfo& event) {
TRACE_EVENT0("input", "MouseWheelEventQueue::QueueEvent");
if (event_sent_for_gesture_ack_ && !wheel_queue_.empty()) {
QueuedWebMouseWheelEvent* last_event = wheel_queue_.back();
if (last_event->CanCoalesceWith(event)) {
last_event->CoalesceWith(event);
TRACE_EVENT_INSTANT2("input", "MouseWheelEventQueue::CoalescedWheelEvent",
TRACE_EVENT_SCOPE_THREAD, "total_dx",
last_event->event.deltaX, "total_dy",
last_event->event.deltaY);
return;
}
}
wheel_queue_.push_back(new QueuedWebMouseWheelEvent(event));
TryForwardNextEventToRenderer();
LOCAL_HISTOGRAM_COUNTS_100("Renderer.WheelQueueSize", wheel_queue_.size());
}
void MouseWheelEventQueue::ProcessMouseWheelAck(
InputEventAckState ack_result,
const LatencyInfo& latency_info) {
TRACE_EVENT0("input", "MouseWheelEventQueue::ProcessMouseWheelAck");
if (!event_sent_for_gesture_ack_)
return;
event_sent_for_gesture_ack_->latency.AddNewLatencyFrom(latency_info);
client_->OnMouseWheelEventAck(*event_sent_for_gesture_ack_, ack_result);
// If event wasn't consumed then generate a gesture scroll for it.
if (ack_result != INPUT_EVENT_ACK_STATE_CONSUMED &&
WebInputEventTraits::CanCauseScroll(event_sent_for_gesture_ack_->event) &&
event_sent_for_gesture_ack_->event.resendingPluginId == -1 &&
(scrolling_device_ == blink::WebGestureDeviceUninitialized ||
scrolling_device_ == blink::WebGestureDeviceTouchpad)) {
WebGestureEvent scroll_update;
scroll_update.timeStampSeconds =
event_sent_for_gesture_ack_->event.timeStampSeconds;
scroll_update.x = event_sent_for_gesture_ack_->event.x;
scroll_update.y = event_sent_for_gesture_ack_->event.y;
scroll_update.globalX = event_sent_for_gesture_ack_->event.globalX;
scroll_update.globalY = event_sent_for_gesture_ack_->event.globalY;
scroll_update.type = WebInputEvent::GestureScrollUpdate;
scroll_update.sourceDevice = blink::WebGestureDeviceTouchpad;
scroll_update.resendingPluginId = -1;
scroll_update.data.scrollUpdate.deltaX =
event_sent_for_gesture_ack_->event.deltaX;
scroll_update.data.scrollUpdate.deltaY =
event_sent_for_gesture_ack_->event.deltaY;
// Only OSX populates the phase and momentumPhase; so
// |inertialPhase| will be UnknownMomentumPhase on all other platforms.
if (event_sent_for_gesture_ack_->event.momentumPhase !=
blink::WebMouseWheelEvent::PhaseNone) {
scroll_update.data.scrollUpdate.inertialPhase =
WebGestureEvent::MomentumPhase;
} else if (event_sent_for_gesture_ack_->event.phase !=
blink::WebMouseWheelEvent::PhaseNone) {
scroll_update.data.scrollUpdate.inertialPhase =
WebGestureEvent::NonMomentumPhase;
}
if (event_sent_for_gesture_ack_->event.scrollByPage) {
scroll_update.data.scrollUpdate.deltaUnits = WebGestureEvent::Page;
// Turn page scrolls into a *single* page scroll because
// the magnitude the number of ticks is lost when coalescing.
if (scroll_update.data.scrollUpdate.deltaX)
scroll_update.data.scrollUpdate.deltaX =
scroll_update.data.scrollUpdate.deltaX > 0 ? 1 : -1;
if (scroll_update.data.scrollUpdate.deltaY)
scroll_update.data.scrollUpdate.deltaY =
scroll_update.data.scrollUpdate.deltaY > 0 ? 1 : -1;
} else {
scroll_update.data.scrollUpdate.deltaUnits =
event_sent_for_gesture_ack_->event.hasPreciseScrollingDeltas
? WebGestureEvent::PrecisePixels
: WebGestureEvent::Pixels;
if (event_sent_for_gesture_ack_->event.railsMode ==
WebInputEvent::RailsModeVertical)
scroll_update.data.scrollUpdate.deltaX = 0;
if (event_sent_for_gesture_ack_->event.railsMode ==
WebInputEvent::RailsModeHorizontal)
scroll_update.data.scrollUpdate.deltaY = 0;
}
bool current_phase_ended = false;
bool has_phase_info = false;
if (event_sent_for_gesture_ack_->event.phase !=
blink::WebMouseWheelEvent::PhaseNone ||
event_sent_for_gesture_ack_->event.momentumPhase !=
blink::WebMouseWheelEvent::PhaseNone) {
has_phase_info = true;
current_phase_ended = event_sent_for_gesture_ack_->event.phase ==
blink::WebMouseWheelEvent::PhaseEnded ||
event_sent_for_gesture_ack_->event.phase ==
blink::WebMouseWheelEvent::PhaseCancelled ||
event_sent_for_gesture_ack_->event.momentumPhase ==
blink::WebMouseWheelEvent::PhaseEnded ||
event_sent_for_gesture_ack_->event.momentumPhase ==
blink::WebMouseWheelEvent::PhaseCancelled;
}
bool needs_update = scroll_update.data.scrollUpdate.deltaX != 0 ||
scroll_update.data.scrollUpdate.deltaY != 0;
// If there is no update to send and the current phase is ended yet a GSB
// needs to be sent, this event sequence doesn't need to be generated
// because the events generated will be a GSB (non-synthetic) and GSE
// (non-synthetic). This situation arises when OSX generates double
// phase end information.
bool empty_sequence =
!needs_update && needs_scroll_begin_ && current_phase_ended;
if (needs_update || !empty_sequence) {
if (needs_scroll_begin_) {
// If no GSB has been sent, it will be a non-synthetic GSB.
SendScrollBegin(scroll_update, false);
} else if (has_phase_info) {
// If a GSB has been sent, generate a synthetic GSB if we have phase
// information. This should be removed once crbug.com/526463 is fully
// implemented.
SendScrollBegin(scroll_update, true);
}
if (needs_update) {
ui::LatencyInfo latency = ui::LatencyInfo();
latency.AddLatencyNumber(
ui::INPUT_EVENT_LATENCY_GENERATE_SCROLL_UPDATE_FROM_MOUSE_WHEEL, 0,
0);
client_->ForwardGestureEventWithLatencyInfo(scroll_update, latency);
}
if (current_phase_ended) {
// Non-synthetic GSEs are sent when the current phase is canceled or
// ended.
SendScrollEnd(scroll_update, false);
} else if (has_phase_info) {
// Generate a synthetic GSE for every update to force hit testing so
// that the non-latching behavior is preserved. Remove once
// crbug.com/526463 is fully implemented.
SendScrollEnd(scroll_update, true);
} else {
scroll_end_timer_.Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(scroll_transaction_ms_),
base::Bind(&MouseWheelEventQueue::SendScrollEnd,
base::Unretained(this), scroll_update, false));
}
}
}
event_sent_for_gesture_ack_.reset();
TryForwardNextEventToRenderer();
}
void MouseWheelEventQueue::OnGestureScrollEvent(
const GestureEventWithLatencyInfo& gesture_event) {
if (gesture_event.event.type == blink::WebInputEvent::GestureScrollBegin) {
// If there is a current scroll going on and a new scroll that isn't
// wheel based cancel current one by sending a ScrollEnd.
if (scroll_end_timer_.IsRunning() &&
gesture_event.event.sourceDevice != blink::WebGestureDeviceTouchpad) {
base::Closure task = scroll_end_timer_.user_task();
scroll_end_timer_.Reset();
task.Run();
}
scrolling_device_ = gesture_event.event.sourceDevice;
} else if (scrolling_device_ == gesture_event.event.sourceDevice &&
(gesture_event.event.type ==
blink::WebInputEvent::GestureScrollEnd ||
gesture_event.event.type ==
blink::WebInputEvent::GestureFlingStart)) {
scrolling_device_ = blink::WebGestureDeviceUninitialized;
if (scroll_end_timer_.IsRunning())
scroll_end_timer_.Reset();
}
}
void MouseWheelEventQueue::TryForwardNextEventToRenderer() {
TRACE_EVENT0("input", "MouseWheelEventQueue::TryForwardNextEventToRenderer");
if (wheel_queue_.empty() || event_sent_for_gesture_ack_)
return;
event_sent_for_gesture_ack_.reset(wheel_queue_.front());
wheel_queue_.pop_front();
client_->SendMouseWheelEventImmediately(*event_sent_for_gesture_ack_);
}
void MouseWheelEventQueue::SendScrollEnd(WebGestureEvent update_event,
bool synthetic) {
DCHECK((synthetic && !needs_scroll_end_) || needs_scroll_end_);
WebGestureEvent scroll_end(update_event);
scroll_end.timeStampSeconds =
(base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
scroll_end.type = WebInputEvent::GestureScrollEnd;
scroll_end.resendingPluginId = -1;
scroll_end.data.scrollEnd.synthetic = synthetic;
scroll_end.data.scrollEnd.inertialPhase =
update_event.data.scrollUpdate.inertialPhase;
scroll_end.data.scrollEnd.deltaUnits =
update_event.data.scrollUpdate.deltaUnits;
if (!synthetic) {
needs_scroll_begin_ = true;
needs_scroll_end_ = false;
if (scroll_end_timer_.IsRunning())
scroll_end_timer_.Reset();
}
client_->ForwardGestureEventWithLatencyInfo(scroll_end, ui::LatencyInfo());
}
void MouseWheelEventQueue::SendScrollBegin(
const WebGestureEvent& gesture_update,
bool synthetic) {
DCHECK((synthetic && !needs_scroll_begin_) || needs_scroll_begin_);
WebGestureEvent scroll_begin(gesture_update);
scroll_begin.type = WebInputEvent::GestureScrollBegin;
scroll_begin.data.scrollBegin.synthetic = synthetic;
scroll_begin.data.scrollBegin.inertialPhase =
gesture_update.data.scrollUpdate.inertialPhase;
scroll_begin.data.scrollBegin.deltaXHint =
gesture_update.data.scrollUpdate.deltaX;
scroll_begin.data.scrollBegin.deltaYHint =
gesture_update.data.scrollUpdate.deltaY;
scroll_begin.data.scrollBegin.targetViewport = false;
scroll_begin.data.scrollBegin.deltaHintUnits =
gesture_update.data.scrollUpdate.deltaUnits;
needs_scroll_begin_ = false;
needs_scroll_end_ = true;
client_->ForwardGestureEventWithLatencyInfo(scroll_begin, ui::LatencyInfo());
}
} // namespace content