blob: 45ee0814ff84418deb0e5bddfcc98afc7bde581f [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/renderer/input/main_thread_event_queue.h"
#include "base/metrics/histogram_macros.h"
#include "content/common/input/event_with_latency_info.h"
#include "content/common/input_messages.h"
namespace content {
namespace {
const size_t kTenSeconds = 10 * 1000 * 1000;
bool IsContinuousEvent(const std::unique_ptr<EventWithDispatchType>& event) {
switch (event->event().type) {
case blink::WebInputEvent::MouseMove:
case blink::WebInputEvent::MouseWheel:
return true;
case blink::WebInputEvent::TouchMove:
// TouchMoves that are blocking end up blocking scroll. Do not treat
// them as continuous events otherwise we will end up waiting up to an
// additional frame.
return static_cast<const blink::WebTouchEvent&>(event->event())
.dispatchType != blink::WebInputEvent::Blocking;
default:
return false;
}
}
} // namespace
EventWithDispatchType::EventWithDispatchType(
ui::ScopedWebInputEvent event,
const ui::LatencyInfo& latency,
InputEventDispatchType dispatch_type)
: ScopedWebInputEventWithLatencyInfo(std::move(event), latency),
dispatch_type_(dispatch_type),
creation_timestamp_(base::TimeTicks::Now()),
last_coalesced_timestamp_(creation_timestamp_) {}
EventWithDispatchType::~EventWithDispatchType() {}
bool EventWithDispatchType::CanCoalesceWith(
const EventWithDispatchType& other) const {
return other.dispatch_type_ == dispatch_type_ &&
ScopedWebInputEventWithLatencyInfo::CanCoalesceWith(other);
}
void EventWithDispatchType::CoalesceWith(const EventWithDispatchType& other) {
coalesced_event_ids_.push_back(
ui::WebInputEventTraits::GetUniqueTouchEventId(other.event()));
ScopedWebInputEventWithLatencyInfo::CoalesceWith(other);
last_coalesced_timestamp_ = base::TimeTicks::Now();
}
MainThreadEventQueue::SharedState::SharedState()
: sent_main_frame_request_(false) {}
MainThreadEventQueue::SharedState::~SharedState() {}
MainThreadEventQueue::MainThreadEventQueue(
int routing_id,
MainThreadEventQueueClient* client,
const scoped_refptr<base::SingleThreadTaskRunner>& main_task_runner,
blink::scheduler::RendererScheduler* renderer_scheduler)
: routing_id_(routing_id),
client_(client),
last_touch_start_forced_nonblocking_due_to_fling_(false),
enable_fling_passive_listener_flag_(base::FeatureList::IsEnabled(
features::kPassiveEventListenersDueToFling)),
handle_raf_aligned_touch_input_(
base::FeatureList::IsEnabled(features::kRafAlignedTouchInputEvents)),
handle_raf_aligned_mouse_input_(
base::FeatureList::IsEnabled(features::kRafAlignedMouseInputEvents)),
main_task_runner_(main_task_runner),
renderer_scheduler_(renderer_scheduler) {}
MainThreadEventQueue::~MainThreadEventQueue() {}
bool MainThreadEventQueue::HandleEvent(
ui::ScopedWebInputEvent event,
const ui::LatencyInfo& latency,
InputEventDispatchType original_dispatch_type,
InputEventAckState ack_result) {
DCHECK(original_dispatch_type == DISPATCH_TYPE_BLOCKING ||
original_dispatch_type == DISPATCH_TYPE_NON_BLOCKING);
DCHECK(ack_result == INPUT_EVENT_ACK_STATE_SET_NON_BLOCKING ||
ack_result == INPUT_EVENT_ACK_STATE_SET_NON_BLOCKING_DUE_TO_FLING ||
ack_result == INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
bool non_blocking = original_dispatch_type == DISPATCH_TYPE_NON_BLOCKING ||
ack_result == INPUT_EVENT_ACK_STATE_SET_NON_BLOCKING;
bool is_wheel = event->type == blink::WebInputEvent::MouseWheel;
bool is_touch = blink::WebInputEvent::isTouchEventType(event->type);
if (is_touch) {
blink::WebTouchEvent* touch_event =
static_cast<blink::WebTouchEvent*>(event.get());
// Adjust the |dispatchType| on the event since the compositor
// determined all event listeners are passive.
if (non_blocking) {
touch_event->dispatchType =
blink::WebInputEvent::ListenersNonBlockingPassive;
}
if (touch_event->type == blink::WebInputEvent::TouchStart)
last_touch_start_forced_nonblocking_due_to_fling_ = false;
if (enable_fling_passive_listener_flag_ &&
touch_event->touchStartOrFirstTouchMove &&
touch_event->dispatchType == blink::WebInputEvent::Blocking) {
// If the touch start is forced to be passive due to fling, its following
// touch move should also be passive.
if (ack_result == INPUT_EVENT_ACK_STATE_SET_NON_BLOCKING_DUE_TO_FLING ||
last_touch_start_forced_nonblocking_due_to_fling_) {
touch_event->dispatchType =
blink::WebInputEvent::ListenersForcedNonBlockingDueToFling;
non_blocking = true;
last_touch_start_forced_nonblocking_due_to_fling_ = true;
}
}
}
if (is_wheel && non_blocking) {
// Adjust the |dispatchType| on the event since the compositor
// determined all event listeners are passive.
static_cast<blink::WebMouseWheelEvent*>(event.get())
->dispatchType = blink::WebInputEvent::ListenersNonBlockingPassive;
}
InputEventDispatchType dispatch_type =
non_blocking ? DISPATCH_TYPE_NON_BLOCKING : DISPATCH_TYPE_BLOCKING;
std::unique_ptr<EventWithDispatchType> event_with_dispatch_type(
new EventWithDispatchType(std::move(event), latency, dispatch_type));
QueueEvent(std::move(event_with_dispatch_type));
// send an ack when we are non-blocking.
return non_blocking;
}
void MainThreadEventQueue::DispatchInFlightEvent() {
if (in_flight_event_) {
// Report the coalesced count only for continuous events; otherwise
// the zero value would be dominated by non-continuous events.
base::TimeTicks now = base::TimeTicks::Now();
if (IsContinuousEvent(in_flight_event_)) {
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Event.MainThreadEventQueue.Continuous.QueueingTime",
(now - in_flight_event_->creationTimestamp()).InMicroseconds(), 1,
kTenSeconds, 50);
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Event.MainThreadEventQueue.Continuous.FreshnessTime",
(now - in_flight_event_->lastCoalescedTimestamp()).InMicroseconds(),
1, kTenSeconds, 50);
UMA_HISTOGRAM_COUNTS_1000("Event.MainThreadEventQueue.CoalescedCount",
in_flight_event_->coalescedEventIds().size());
} else {
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Event.MainThreadEventQueue.NonContinuous.QueueingTime",
(now - in_flight_event_->creationTimestamp()).InMicroseconds(), 1,
kTenSeconds, 50);
}
InputEventDispatchType dispatch_type = in_flight_event_->dispatchType();
if (!in_flight_event_->coalescedEventIds().empty() &&
dispatch_type == DISPATCH_TYPE_BLOCKING) {
dispatch_type = DISPATCH_TYPE_BLOCKING_NOTIFY_MAIN;
}
client_->HandleEventOnMainThread(routing_id_, &in_flight_event_->event(),
in_flight_event_->latencyInfo(),
dispatch_type);
}
in_flight_event_.reset();
}
void MainThreadEventQueue::PossiblyScheduleMainFrame() {
if (IsRafAlignedInputDisabled())
return;
bool needs_main_frame = false;
{
base::AutoLock lock(shared_state_lock_);
if (!shared_state_.sent_main_frame_request_ &&
!shared_state_.events_.empty() &&
IsRafAlignedEvent(shared_state_.events_.front())) {
needs_main_frame = !shared_state_.sent_main_frame_request_;
shared_state_.sent_main_frame_request_ = false;
}
}
if (needs_main_frame)
client_->NeedsMainFrame(routing_id_);
}
void MainThreadEventQueue::DispatchSingleEvent() {
{
base::AutoLock lock(shared_state_lock_);
if (shared_state_.events_.empty())
return;
in_flight_event_ = shared_state_.events_.Pop();
}
DispatchInFlightEvent();
PossiblyScheduleMainFrame();
}
void MainThreadEventQueue::EventHandled(blink::WebInputEvent::Type type,
InputEventAckState ack_result) {
if (in_flight_event_ &&
in_flight_event_->dispatchType() == DISPATCH_TYPE_BLOCKING) {
for (const auto id : in_flight_event_->coalescedEventIds()) {
client_->SendInputEventAck(routing_id_, type, ack_result, id);
if (renderer_scheduler_) {
renderer_scheduler_->DidHandleInputEventOnMainThread(
in_flight_event_->event());
}
}
}
}
void MainThreadEventQueue::DispatchRafAlignedInput() {
if (IsRafAlignedInputDisabled())
return;
std::deque<std::unique_ptr<EventWithDispatchType>> events_to_process;
{
base::AutoLock lock(shared_state_lock_);
shared_state_.sent_main_frame_request_ = false;
while(!shared_state_.events_.empty()) {
if (!IsRafAlignedEvent(shared_state_.events_.front()))
break;
events_to_process.emplace_back(shared_state_.events_.Pop());
}
}
while(!events_to_process.empty()) {
in_flight_event_ = std::move(events_to_process.front());
events_to_process.pop_front();
DispatchInFlightEvent();
}
PossiblyScheduleMainFrame();
}
void MainThreadEventQueue::SendEventNotificationToMainThread() {
main_task_runner_->PostTask(
FROM_HERE, base::Bind(&MainThreadEventQueue::DispatchSingleEvent, this));
}
void MainThreadEventQueue::QueueEvent(
std::unique_ptr<EventWithDispatchType> event) {
bool is_raf_aligned = IsRafAlignedEvent(event);
size_t send_notification_count = 0;
bool needs_main_frame = false;
{
base::AutoLock lock(shared_state_lock_);
size_t size_before = shared_state_.events_.size();
shared_state_.events_.Queue(std::move(event));
size_t size_after = shared_state_.events_.size();
if (size_before != size_after) {
if (IsRafAlignedInputDisabled()) {
send_notification_count = 1;
} else if (!is_raf_aligned) {
send_notification_count = 1;
// If we had just enqueued a non-rAF input event we will send a series
// of normal post messages to ensure they are all handled right away.
for (size_t pos = size_after - 1; pos >= 1; --pos) {
if (IsRafAlignedEvent(shared_state_.events_.at(pos - 1)))
send_notification_count++;
else
break;
}
} else {
needs_main_frame = !shared_state_.sent_main_frame_request_;
shared_state_.sent_main_frame_request_ = true;
}
}
}
for (size_t i = 0; i < send_notification_count; ++i)
SendEventNotificationToMainThread();
if (needs_main_frame)
client_->NeedsMainFrame(routing_id_);
}
bool MainThreadEventQueue::IsRafAlignedInputDisabled() {
return !handle_raf_aligned_mouse_input_ && !handle_raf_aligned_touch_input_;
}
bool MainThreadEventQueue::IsRafAlignedEvent(
const std::unique_ptr<EventWithDispatchType>& event) {
switch (event->event().type) {
case blink::WebInputEvent::MouseMove:
case blink::WebInputEvent::MouseWheel:
return handle_raf_aligned_mouse_input_;
case blink::WebInputEvent::TouchMove:
// TouchMoves that are blocking end up blocking scroll. Do not treat
// them as continuous events otherwise we will end up waiting up to an
// additional frame.
return static_cast<const blink::WebTouchEvent&>(event->event())
.dispatchType != blink::WebInputEvent::Blocking &&
handle_raf_aligned_touch_input_;
default:
return false;
}
}
} // namespace content