| Index: content/renderer/input/main_thread_event_queue.cc
 | 
| diff --git a/content/renderer/input/main_thread_event_queue.cc b/content/renderer/input/main_thread_event_queue.cc
 | 
| index 8c1b8675cfafb469d36ae1841c51a4d9256fe32e..5c872c0ef2cc04a65eaf42274ce5ee0c57b574ae 100644
 | 
| --- a/content/renderer/input/main_thread_event_queue.cc
 | 
| +++ b/content/renderer/input/main_thread_event_queue.cc
 | 
| @@ -4,17 +4,43 @@
 | 
|  
 | 
|  #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 {
 | 
| +
 | 
| +// The maximum number of post-coalesced events processed per rAF task. 10 was
 | 
| +// chosen because it really should never be hit yet prevents an infinite loop if
 | 
| +// the compositor keeps delivering events faster than the main thread can
 | 
| +// process them.
 | 
| +const size_t kMaxEventsPerRafTask = 10;
 | 
| +
 | 
| +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::TouchMove:
 | 
| +    case blink::WebInputEvent::MouseWheel:
 | 
| +      return true;
 | 
| +    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) {}
 | 
| +      dispatch_type_(dispatch_type),
 | 
| +      creation_timestamp_(base::TimeTicks::Now()),
 | 
| +      last_coalesced_timestamp_(creation_timestamp_) {}
 | 
|  
 | 
|  EventWithDispatchType::~EventWithDispatchType() {}
 | 
|  
 | 
| @@ -25,16 +51,17 @@ bool EventWithDispatchType::CanCoalesceWith(
 | 
|  }
 | 
|  
 | 
|  void EventWithDispatchType::CoalesceWith(const EventWithDispatchType& other) {
 | 
| -  // If we are blocking and are coalescing touch, make sure to keep
 | 
| -  // the touch ids that need to be acked.
 | 
| -  if (dispatch_type_ == DISPATCH_TYPE_BLOCKING) {
 | 
| -    // We should only have blocking touch events that need coalescing.
 | 
| -    eventsToAck_.push_back(
 | 
| -        ui::WebInputEventTraits::GetUniqueTouchEventId(other.event()));
 | 
| -  }
 | 
| +  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,
 | 
| @@ -46,6 +73,8 @@ MainThreadEventQueue::MainThreadEventQueue(
 | 
|        last_touch_start_forced_nonblocking_due_to_fling_(false),
 | 
|        enable_fling_passive_listener_flag_(base::FeatureList::IsEnabled(
 | 
|            features::kPassiveEventListenersDueToFling)),
 | 
| +      handle_raf_aligned_input_(
 | 
| +          base::FeatureList::IsEnabled(features::kRafAlignedInputEvents)),
 | 
|        main_task_runner_(main_task_runner),
 | 
|        renderer_scheduler_(renderer_scheduler) {}
 | 
|  
 | 
| @@ -110,16 +139,33 @@ bool MainThreadEventQueue::HandleEvent(
 | 
|    return non_blocking;
 | 
|  }
 | 
|  
 | 
| -void MainThreadEventQueue::PopEventOnMainThread() {
 | 
| -  {
 | 
| -    base::AutoLock lock(event_queue_lock_);
 | 
| -    if (!events_.empty())
 | 
| -      in_flight_event_ = events_.Pop();
 | 
| -  }
 | 
| -
 | 
| +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_->eventsToAck().empty() &&
 | 
| +    if (!in_flight_event_->coalescedEventIds().empty() &&
 | 
|          dispatch_type == DISPATCH_TYPE_BLOCKING) {
 | 
|        dispatch_type = DISPATCH_TYPE_BLOCKING_NOTIFY_MAIN;
 | 
|      }
 | 
| @@ -132,11 +178,40 @@ void MainThreadEventQueue::PopEventOnMainThread() {
 | 
|    in_flight_event_.reset();
 | 
|  }
 | 
|  
 | 
| +void MainThreadEventQueue::PossiblyScheduleMainFrame() {
 | 
| +  if (!handle_raf_aligned_input_)
 | 
| +    return;
 | 
| +  bool needs_main_frame = false;
 | 
| +  {
 | 
| +    base::AutoLock lock(shared_state_lock_);
 | 
| +    if (!shared_state_.sent_main_frame_request_ &&
 | 
| +        !shared_state_.events_.empty() &&
 | 
| +        isContinuousEvent(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_) {
 | 
| -    // Send acks for blocking touch events.
 | 
| -    for (const auto id : in_flight_event_->eventsToAck()) {
 | 
| +  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(
 | 
| @@ -146,23 +221,68 @@ void MainThreadEventQueue::EventHandled(blink::WebInputEvent::Type type,
 | 
|    }
 | 
|  }
 | 
|  
 | 
| +void MainThreadEventQueue::DispatchRafAlignedInput() {
 | 
| +  if (!handle_raf_aligned_input_)
 | 
| +    return;
 | 
| +
 | 
| +  {
 | 
| +    base::AutoLock lock(shared_state_lock_);
 | 
| +    shared_state_.sent_main_frame_request_ = false;
 | 
| +  }
 | 
| +
 | 
| +  for (size_t i = 0; i < kMaxEventsPerRafTask; ++i) {
 | 
| +    {
 | 
| +      base::AutoLock lock(shared_state_lock_);
 | 
| +      if (shared_state_.events_.empty())
 | 
| +        break;
 | 
| +
 | 
| +      if (!isContinuousEvent(shared_state_.events_.front()))
 | 
| +        break;
 | 
| +      in_flight_event_ = shared_state_.events_.Pop();
 | 
| +    }
 | 
| +    DispatchInFlightEvent();
 | 
| +  }
 | 
| +  PossiblyScheduleMainFrame();
 | 
| +}
 | 
| +
 | 
|  void MainThreadEventQueue::SendEventNotificationToMainThread() {
 | 
|    main_task_runner_->PostTask(
 | 
| -      FROM_HERE, base::Bind(&MainThreadEventQueue::PopEventOnMainThread,
 | 
| -                            this));
 | 
| +      FROM_HERE, base::Bind(&MainThreadEventQueue::DispatchSingleEvent, this));
 | 
|  }
 | 
|  
 | 
|  void MainThreadEventQueue::QueueEvent(
 | 
|      std::unique_ptr<EventWithDispatchType> event) {
 | 
| -  bool send_notification = false;
 | 
| +  bool is_continuous = isContinuousEvent(event);
 | 
| +  size_t send_notification_count = 0;
 | 
| +  bool needs_main_frame = false;
 | 
|    {
 | 
| -    base::AutoLock lock(event_queue_lock_);
 | 
| -    size_t size_before = events_.size();
 | 
| -    events_.Queue(std::move(event));
 | 
| -    send_notification = events_.size() != size_before;
 | 
| +    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 (!handle_raf_aligned_input_) {
 | 
| +        send_notification_count = 1;
 | 
| +      } else if (!is_continuous) {
 | 
| +        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 (isContinuousEvent(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;
 | 
| +      }
 | 
| +    }
 | 
|    }
 | 
| -  if (send_notification)
 | 
| +  for (size_t i = 0; i < send_notification_count; ++i)
 | 
|      SendEventNotificationToMainThread();
 | 
| +  if (needs_main_frame)
 | 
| +    client_->NeedsMainFrame(routing_id_);
 | 
|  }
 | 
|  
 | 
|  }  // namespace content
 | 
| 
 |