c++google-chromechromium

rAF-aligned pointermove event


I studied event behavior and also read the Chromium source code. And the following surprised me.

Usually, if you move the pointer within a document, you'll get this behavior: render task
This is the so-called rAF-aligned event, and it's located within the render task (this is the de facto behavior in Chrome).

But I managed to achieve a completely different behavior when the pointermove event occurs before the render task, in its own task: dedicated task
So the real question is: when is this possible? And how is this explained by the Chromium source code?


I have a hunch that maybe this function does something interesting:

void MainThreadEventQueue::QueueEvent(
    std::unique_ptr<MainThreadEventQueueTask> event) {
  bool is_raf_aligned = IsRafAlignedEvent(event);
  bool needs_main_frame = false;
  bool needs_post_task = false;

  // Record the input event's type prior to enqueueing so that the scheduler
  // can be notified of its dispatch (if the event is not coalesced).
  bool is_input_event = event->IsWebInputEvent();
  WebInputEvent::Type input_event_type = WebInputEvent::Type::kUndefined;
  WebInputEventAttribution attribution;
  if (is_input_event) {
    auto* queued_input_event = static_cast<QueuedWebInputEvent*>(event.get());
    input_event_type = queued_input_event->Event().GetType();
    attribution = queued_input_event->attribution();
    queued_input_event->SetQueuedTimeStamp(base::TimeTicks::Now());
  }

  {
    base::AutoLock lock(shared_state_lock_);

    if (shared_state_.events_.Enqueue(std::move(event)) ==
        MainThreadEventQueueTaskList::EnqueueResult::kEnqueued) {
      if (!is_raf_aligned) {
        needs_post_task = !shared_state_.sent_post_task_;
        shared_state_.sent_post_task_ = true;
      } else {
        needs_main_frame = !shared_state_.sent_main_frame_request_;
        shared_state_.sent_main_frame_request_ = true;
      }

      // Notify the scheduler that we'll enqueue a task to the main thread.
      if (is_input_event) {
        widget_scheduler_->WillPostInputEventToMainThread(input_event_type,
                                                          attribution);
      }
    }
  }

  if (needs_post_task)
    PostTaskToMainThread();
  if (needs_main_frame) {
    // This main frame request is coming from input, make it urgent.
    //
    // We only ever want to consider urgent frames for clients which could have
    // main frames throttled, hence we check the eligibility.
    bool urgent =
        ::features::IsEligibleForThrottleMainFrameTo60Hz() &&
        base::FeatureList::IsEnabled(blink::features::kUrgentMainFrameForInput);
    SetNeedsMainFrame(urgent);
  }
}

But I'm not sure I fully understand this code. If anyone understands what's going on, I'd be interested to hear about it.


Solution

  • Since I managed to find the answer, I'll share it for those curious:

    From the code I provided, it's clear that the pointermove event is sent to the event queue. It's also clear that the compositor schedules the frame task based on the pointermove event.

    If only the pointermove event were present, the pointermove would be in the rendering task.

    However, if events appear in the queue after the pointermove that aren't raf-aligned, such as pointerout or pointerdown, they cancel the alignment and schedule the task. Pointerdown needs to be processed as quickly as possible, and it's in the queue after the pointermove, so the pointermove will be processed in the task before rendering in the task which might be scheduled by pointerdown.

    This snippet of code shows how it selects the number of events in the queue that will be executed in the task (not the render task):

    events_to_process = shared_state_.events_.size();
    // Don't process rAF aligned events at tail of queue.
    while (events_to_process > 0 && !ShouldFlushQueue(shared_state_.events_.at(events_to_process - 1))) {
          --events_to_process;
    }
    

    And this snippet already shows the execution of the events:

    while (events_to_process--) {
        std::unique_ptr<MainThreadEventQueueTask> task;
        {
          base::AutoLock lock(shared_state_lock_);
          if (shared_state_.events_.empty())
            return;
          task = shared_state_.events_.Pop();
        }
    
        HandleEventResampling(task, base::TimeTicks::Now());
        // Dispatching the event is outside of critical section.
        task->Dispatch(this);
      }
    

    Code above is here