blob: b629b408ae2ed0a912d116d889855904c9d3a89b [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/field_trial.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.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:
case blink::WebInputEvent::TouchMove:
return true;
default:
return false;
}
}
} // namespace
EventWithDispatchType::EventWithDispatchType(
ui::WebScopedInputEvent event,
const ui::LatencyInfo& latency,
InputEventDispatchType dispatch_type)
: ScopedWebInputEventWithLatencyInfo(std::move(event), latency),
dispatch_type_(dispatch_type),
non_blocking_coalesced_count_(0),
creation_timestamp_(base::TimeTicks::Now()),
last_coalesced_timestamp_(creation_timestamp_) {}
EventWithDispatchType::~EventWithDispatchType() {}
void EventWithDispatchType::CoalesceWith(const EventWithDispatchType& other) {
if (other.dispatch_type_ == DISPATCH_TYPE_BLOCKING) {
blocking_coalesced_event_ids_.push_back(
ui::WebInputEventTraits::GetUniqueTouchEventId(other.event()));
} else {
non_blocking_coalesced_count_++;
}
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)),
enable_non_blocking_due_to_main_thread_responsiveness_flag_(
base::FeatureList::IsEnabled(
features::kMainThreadBusyScrollIntervention)),
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) {
if (enable_non_blocking_due_to_main_thread_responsiveness_flag_) {
std::string group = base::FieldTrialList::FindFullName(
"MainThreadResponsivenessScrollIntervention");
// The group name will be of the form Enabled$THRESHOLD_MS. Trim the prefix
// "Enabled", and parse the threshold.
int threshold_ms = 0;
std::string prefix = "Enabled";
group.erase(0, prefix.length());
base::StringToInt(group, &threshold_ms);
if (threshold_ms <= 0) {
enable_non_blocking_due_to_main_thread_responsiveness_flag_ = false;
} else {
main_thread_responsiveness_threshold_ =
base::TimeDelta::FromMilliseconds(threshold_ms);
}
}
}
MainThreadEventQueue::~MainThreadEventQueue() {}
bool MainThreadEventQueue::HandleEvent(
ui::WebScopedInputEvent 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 (enable_non_blocking_due_to_main_thread_responsiveness_flag_ &&
touch_event->dispatchType == blink::WebInputEvent::Blocking) {
bool passive_due_to_unresponsive_main =
renderer_scheduler_->MainThreadSeemsUnresponsive(
main_thread_responsiveness_threshold_);
if (passive_due_to_unresponsive_main) {
touch_event->dispatchType = blink::WebInputEvent::
ListenersForcedNonBlockingDueToMainThreadResponsiveness;
non_blocking = true;
}
}
// If the event is non-cancelable ACK it right away.
if (!non_blocking &&
touch_event->dispatchType != blink::WebInputEvent::Blocking)
non_blocking = 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_->coalescedCount());
} 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_->blockingCoalescedEventIds().empty()) {
switch (dispatch_type) {
case DISPATCH_TYPE_BLOCKING:
dispatch_type = DISPATCH_TYPE_BLOCKING_NOTIFY_MAIN;
break;
case DISPATCH_TYPE_NON_BLOCKING:
dispatch_type = DISPATCH_TYPE_NON_BLOCKING_NOTIFY_MAIN;
break;
default:
NOTREACHED();
}
}
client_->HandleEventOnMainThread(
routing_id_, &in_flight_event_->coalesced_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()->event())) {
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,
blink::WebInputEventResult result,
InputEventAckState ack_result) {
if (in_flight_event_) {
for (const auto id : in_flight_event_->blockingCoalescedEventIds()) {
client_->SendInputEventAck(routing_id_, type, ack_result, id);
if (renderer_scheduler_) {
renderer_scheduler_->DidHandleInputEventOnMainThread(
in_flight_event_->event(), result);
}
}
}
}
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()->event()))
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->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();
// Stash if the tail of the queue was rAF aligned.
bool was_raf_aligned = false;
if (size_before > 0) {
was_raf_aligned =
IsRafAlignedEvent(shared_state_.events_.at(size_before - 1)->event());
}
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)->event()))
send_notification_count++;
else
break;
}
} else {
needs_main_frame = !shared_state_.sent_main_frame_request_;
shared_state_.sent_main_frame_request_ = true;
}
} else if (size_before > 0) {
// The event was coalesced. The queue size didn't change but
// the rAF alignment of the event may have and we need to schedule
// a notification.
bool is_coalesced_raf_aligned =
IsRafAlignedEvent(shared_state_.events_.at(size_before - 1)->event());
if (was_raf_aligned != is_coalesced_raf_aligned)
send_notification_count = 1;
}
}
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 blink::WebInputEvent& event) {
switch (event.type()) {
case blink::WebInputEvent::MouseMove:
case blink::WebInputEvent::MouseWheel:
return handle_raf_aligned_mouse_input_;
case blink::WebInputEvent::TouchMove:
return handle_raf_aligned_touch_input_;
default:
return false;
}
}
} // namespace content