blob: 9b850fd39f809b0641c9fa530132d5a1f753dde1 [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/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/trace_event/trace_event.h"
#include "content/common/input/input_event_dispatch_type.h"
#include "content/public/common/content_features.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/blink/web_input_event_traits.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,
bool enable_scroll_latching)
: client_(client),
needs_scroll_begin_(true),
needs_scroll_end_(false),
enable_scroll_latching_(enable_scroll_latching),
enable_async_wheel_events_(
base::FeatureList::IsEnabled(features::kAsyncWheelEvents)),
send_wheel_events_async_(false),
scrolling_device_(blink::kWebGestureDeviceUninitialized) {
DCHECK(client);
}
MouseWheelEventQueue::~MouseWheelEventQueue() {
}
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().get();
if (last_event->CanCoalesceWith(event)) {
last_event->CoalesceWith(event);
TRACE_EVENT_INSTANT2("input", "MouseWheelEventQueue::CoalescedWheelEvent",
TRACE_EVENT_SCOPE_THREAD, "total_dx",
last_event->event.delta_x, "total_dy",
last_event->event.delta_y);
return;
}
}
wheel_queue_.push_back(base::MakeUnique<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 &&
ui::WebInputEventTraits::CanCauseScroll(
event_sent_for_gesture_ack_->event) &&
event_sent_for_gesture_ack_->event.resending_plugin_id == -1 &&
(scrolling_device_ == blink::kWebGestureDeviceUninitialized ||
scrolling_device_ == blink::kWebGestureDeviceTouchpad)) {
WebGestureEvent scroll_update(
WebInputEvent::kGestureScrollUpdate, WebInputEvent::kNoModifiers,
event_sent_for_gesture_ack_->event.TimeStampSeconds());
scroll_update.x = event_sent_for_gesture_ack_->event.PositionInWidget().x;
scroll_update.y = event_sent_for_gesture_ack_->event.PositionInWidget().y;
scroll_update.global_x =
event_sent_for_gesture_ack_->event.PositionInScreen().x;
scroll_update.global_y =
event_sent_for_gesture_ack_->event.PositionInScreen().y;
scroll_update.source_device = blink::kWebGestureDeviceTouchpad;
scroll_update.resending_plugin_id = -1;
// Swap X & Y if Shift is down and when there is no horizontal movement.
if ((event_sent_for_gesture_ack_->event.GetModifiers() &
WebInputEvent::kShiftKey) != 0 &&
event_sent_for_gesture_ack_->event.delta_x == 0) {
scroll_update.data.scroll_update.delta_x =
event_sent_for_gesture_ack_->event.delta_y;
scroll_update.data.scroll_update.delta_y =
event_sent_for_gesture_ack_->event.delta_x;
} else {
scroll_update.data.scroll_update.delta_x =
event_sent_for_gesture_ack_->event.delta_x;
scroll_update.data.scroll_update.delta_y =
event_sent_for_gesture_ack_->event.delta_y;
}
// Only OSX populates the phase and momentumPhase; so
// |inertialPhase| will be UnknownMomentumPhase on all other platforms.
if (event_sent_for_gesture_ack_->event.momentum_phase !=
blink::WebMouseWheelEvent::kPhaseNone) {
scroll_update.data.scroll_update.inertial_phase =
WebGestureEvent::kMomentumPhase;
} else if (event_sent_for_gesture_ack_->event.phase !=
blink::WebMouseWheelEvent::kPhaseNone) {
scroll_update.data.scroll_update.inertial_phase =
WebGestureEvent::kNonMomentumPhase;
}
if (event_sent_for_gesture_ack_->event.scroll_by_page) {
scroll_update.data.scroll_update.delta_units = WebGestureEvent::kPage;
// Turn page scrolls into a *single* page scroll because
// the magnitude the number of ticks is lost when coalescing.
if (scroll_update.data.scroll_update.delta_x)
scroll_update.data.scroll_update.delta_x =
scroll_update.data.scroll_update.delta_x > 0 ? 1 : -1;
if (scroll_update.data.scroll_update.delta_y)
scroll_update.data.scroll_update.delta_y =
scroll_update.data.scroll_update.delta_y > 0 ? 1 : -1;
} else {
scroll_update.data.scroll_update.delta_units =
event_sent_for_gesture_ack_->event.has_precise_scrolling_deltas
? WebGestureEvent::kPrecisePixels
: WebGestureEvent::kPixels;
if (event_sent_for_gesture_ack_->event.rails_mode ==
WebInputEvent::kRailsModeVertical)
scroll_update.data.scroll_update.delta_x = 0;
if (event_sent_for_gesture_ack_->event.rails_mode ==
WebInputEvent::kRailsModeHorizontal)
scroll_update.data.scroll_update.delta_y = 0;
}
bool current_phase_ended = false;
bool scroll_phase_ended = false;
bool momentum_phase_ended = false;
bool has_phase_info = false;
if (event_sent_for_gesture_ack_->event.phase !=
blink::WebMouseWheelEvent::kPhaseNone ||
event_sent_for_gesture_ack_->event.momentum_phase !=
blink::WebMouseWheelEvent::kPhaseNone) {
has_phase_info = true;
scroll_phase_ended = event_sent_for_gesture_ack_->event.phase ==
blink::WebMouseWheelEvent::kPhaseEnded ||
event_sent_for_gesture_ack_->event.phase ==
blink::WebMouseWheelEvent::kPhaseCancelled;
momentum_phase_ended =
event_sent_for_gesture_ack_->event.momentum_phase ==
blink::WebMouseWheelEvent::kPhaseEnded ||
event_sent_for_gesture_ack_->event.momentum_phase ==
blink::WebMouseWheelEvent::kPhaseCancelled;
current_phase_ended = scroll_phase_ended || momentum_phase_ended;
}
bool needs_update = scroll_update.data.scroll_update.delta_x != 0 ||
scroll_update.data.scroll_update.delta_y != 0;
if (enable_scroll_latching_) {
if (event_sent_for_gesture_ack_->event.phase ==
blink::WebMouseWheelEvent::kPhaseBegan) {
send_wheel_events_async_ = true;
SendScrollBegin(scroll_update, false);
}
if (needs_update) {
ui::LatencyInfo latency = ui::LatencyInfo(ui::SourceEventType::WHEEL);
latency.AddLatencyNumber(
ui::INPUT_EVENT_LATENCY_GENERATE_SCROLL_UPDATE_FROM_MOUSE_WHEEL, 0,
0);
client_->ForwardGestureEventWithLatencyInfo(scroll_update, latency);
}
if (current_phase_ended) {
// Send GSE with if scroll latching is enabled and no fling is going
// to happen next.
SendScrollEnd(scroll_update, false);
}
} else { // !enable_scroll_latching_
// 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(ui::SourceEventType::WHEEL);
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 if (!has_phase_info) {
SendScrollEnd(scroll_update, false);
}
}
}
}
event_sent_for_gesture_ack_.reset();
TryForwardNextEventToRenderer();
}
void MouseWheelEventQueue::OnGestureScrollEvent(
const GestureEventWithLatencyInfo& gesture_event) {
if (gesture_event.event.GetType() ==
blink::WebInputEvent::kGestureScrollBegin) {
scrolling_device_ = gesture_event.event.source_device;
} else if (scrolling_device_ == gesture_event.event.source_device &&
(gesture_event.event.GetType() ==
blink::WebInputEvent::kGestureScrollEnd ||
gesture_event.event.GetType() ==
blink::WebInputEvent::kGestureFlingStart)) {
scrolling_device_ = blink::kWebGestureDeviceUninitialized;
if (enable_scroll_latching_) {
needs_scroll_begin_ = true;
needs_scroll_end_ = false;
}
}
}
void MouseWheelEventQueue::TryForwardNextEventToRenderer() {
TRACE_EVENT0("input", "MouseWheelEventQueue::TryForwardNextEventToRenderer");
if (wheel_queue_.empty() || event_sent_for_gesture_ack_)
return;
event_sent_for_gesture_ack_ = std::move(wheel_queue_.front());
wheel_queue_.pop_front();
if (enable_async_wheel_events_) {
DCHECK(event_sent_for_gesture_ack_->event.phase !=
blink::WebMouseWheelEvent::kPhaseNone ||
event_sent_for_gesture_ack_->event.momentum_phase !=
blink::WebMouseWheelEvent::kPhaseNone);
if (event_sent_for_gesture_ack_->event.phase ==
blink::WebMouseWheelEvent::kPhaseBegan) {
send_wheel_events_async_ = false;
} else if (send_wheel_events_async_) {
event_sent_for_gesture_ack_->event.dispatch_type =
WebInputEvent::kEventNonBlocking;
}
}
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.SetTimeStampSeconds(
ui::EventTimeStampToSeconds(ui::EventTimeForNow()));
scroll_end.SetType(WebInputEvent::kGestureScrollEnd);
scroll_end.resending_plugin_id = -1;
scroll_end.data.scroll_end.synthetic = synthetic;
scroll_end.data.scroll_end.inertial_phase =
update_event.data.scroll_update.inertial_phase;
scroll_end.data.scroll_end.delta_units =
update_event.data.scroll_update.delta_units;
if (!synthetic) {
needs_scroll_begin_ = true;
needs_scroll_end_ = false;
}
client_->ForwardGestureEventWithLatencyInfo(
scroll_end, ui::LatencyInfo(ui::SourceEventType::WHEEL));
}
void MouseWheelEventQueue::SendScrollBegin(
const WebGestureEvent& gesture_update,
bool synthetic) {
DCHECK((synthetic && !needs_scroll_begin_) || needs_scroll_begin_);
WebGestureEvent scroll_begin(gesture_update);
scroll_begin.SetType(WebInputEvent::kGestureScrollBegin);
scroll_begin.data.scroll_begin.synthetic = synthetic;
scroll_begin.data.scroll_begin.inertial_phase =
gesture_update.data.scroll_update.inertial_phase;
scroll_begin.data.scroll_begin.delta_x_hint =
gesture_update.data.scroll_update.delta_x;
scroll_begin.data.scroll_begin.delta_y_hint =
gesture_update.data.scroll_update.delta_y;
scroll_begin.data.scroll_begin.target_viewport = false;
scroll_begin.data.scroll_begin.delta_hint_units =
gesture_update.data.scroll_update.delta_units;
needs_scroll_begin_ = false;
needs_scroll_end_ = true;
client_->ForwardGestureEventWithLatencyInfo(
scroll_begin, ui::LatencyInfo(ui::SourceEventType::WHEEL));
}
} // namespace content