chromium/ui/accelerated_widget_mac/window_resize_helper_mac.cc

// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/accelerated_widget_mac/window_resize_helper_mac.h"

#include <stdint.h>

#include <list>
#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"

namespace ui {
namespace {

class WrappedTask;
class PumpableTaskRunner;
using WrappedTaskQueue = std::list<WrappedTask*>;
using EventTimedWaitCallback =
    base::RepeatingCallback<void(base::WaitableEvent*, base::TimeDelta)>;

// A wrapper for IPCs and tasks that we may potentially execute in
// WaitForSingleTaskToRun. Because these tasks are sent to two places to run,
// we have to wrap them in this structure and track whether or not they have run
// yet, to avoid running them twice.
class WrappedTask {
 public:
  WrappedTask(base::OnceClosure closure, base::TimeDelta delay);

  WrappedTask(const WrappedTask&) = delete;
  WrappedTask& operator=(const WrappedTask&) = delete;

  ~WrappedTask();
  bool ShouldRunBefore(const WrappedTask& other);
  void Run();
  void AddToTaskRunnerQueue(PumpableTaskRunner* pumpable_task_runner);
  void RemoveFromTaskRunnerQueue();
  const base::TimeTicks& can_run_time() const { return can_run_time_; }

 private:
  base::OnceClosure closure_;
  base::TimeTicks can_run_time_;
  bool has_run_;
  uint64_t sequence_number_;
  WrappedTaskQueue::iterator iterator_;

  // Back pointer to the pumpable task runner that this task is enqueued in.
  scoped_refptr<PumpableTaskRunner> pumpable_task_runner_;
};

// The PumpableTaskRunner is a task runner that will wrap tasks in an
// WrappedTask, enqueues that wrapped task in the queue to be pumped via
// WaitForSingleWrappedTaskToRun during resizes, and posts the task to a
// target task runner. The posted task will run only once, either through a
// WaitForSingleWrappedTaskToRun call or through the target task runner.
class PumpableTaskRunner : public base::SingleThreadTaskRunner {
 public:
  PumpableTaskRunner(
      const EventTimedWaitCallback& event_timed_wait_callback,
      const scoped_refptr<base::SingleThreadTaskRunner>& target_task_runner);

  PumpableTaskRunner(const PumpableTaskRunner&) = delete;
  PumpableTaskRunner& operator=(const PumpableTaskRunner&) = delete;

  // Enqueue WrappedTask and post it to |target_task_runner_|.
  bool EnqueueAndPostWrappedTask(const base::Location& from_here,
                                 std::unique_ptr<WrappedTask> task,
                                 base::TimeDelta delay);

  // Wait at most |max_delay| to run an enqueued task.
  bool WaitForSingleWrappedTaskToRun(const base::TimeDelta& max_delay);

  // base::SingleThreadTaskRunner implementation:
  bool PostDelayedTask(const base::Location& from_here,
                       base::OnceClosure task,
                       base::TimeDelta delay) override;

  bool PostNonNestableDelayedTask(const base::Location& from_here,
                                  base::OnceClosure task,
                                  base::TimeDelta delay) override;

  bool RunsTasksInCurrentSequence() const override;

 private:
  friend class WrappedTask;

  ~PumpableTaskRunner() override;

  // A queue of live messages.  Must hold |task_queue_lock_| to access. Tasks
  // are added only on the IO thread and removed only on the UI thread.  The
  // WrappedTask objects are removed from the queue when they are run (by
  // |target_task_runner_| or by a call to WaitForSingleWrappedTaskToRun
  // removing them out of the queue, or by TaskRunner when it is destroyed).
  WrappedTaskQueue task_queue_;
  base::Lock task_queue_lock_;

  // Event used to wake up the UI thread if it is sleeping in
  // WaitForSingleTaskToRun.
  base::WaitableEvent event_;

  // Callback to call TimedWait on |event_| from an appropriate class.
  EventTimedWaitCallback event_timed_wait_callback_;

  scoped_refptr<base::SingleThreadTaskRunner> target_task_runner_;
};

base::LazyInstance<WindowResizeHelperMac>::Leaky g_window_resize_helper =
    LAZY_INSTANCE_INITIALIZER;

////////////////////////////////////////////////////////////////////////////////
// WrappedTask

WrappedTask::WrappedTask(base::OnceClosure closure, base::TimeDelta delay)
    : closure_(std::move(closure)),
      can_run_time_(base::TimeTicks::Now() + delay),
      has_run_(false),
      sequence_number_(0) {}

WrappedTask::~WrappedTask() {
  RemoveFromTaskRunnerQueue();
}

bool WrappedTask::ShouldRunBefore(const WrappedTask& other) {
  if (can_run_time_ < other.can_run_time_)
    return true;
  if (can_run_time_ > other.can_run_time_)
    return false;
  if (sequence_number_ < other.sequence_number_)
    return true;
  if (sequence_number_ > other.sequence_number_)
    return false;
  // Sequence numbers are unique, so this should never happen.
  NOTREACHED_IN_MIGRATION();
  return false;
}

void WrappedTask::Run() {
  if (has_run_)
    return;
  RemoveFromTaskRunnerQueue();
  has_run_ = true;
  std::move(closure_).Run();
}

void WrappedTask::AddToTaskRunnerQueue(
    PumpableTaskRunner* pumpable_task_runner) {
  pumpable_task_runner_ = pumpable_task_runner;
  base::AutoLock lock(pumpable_task_runner_->task_queue_lock_);
  static uint64_t last_sequence_number = 0;
  last_sequence_number += 1;
  sequence_number_ = last_sequence_number;
  iterator_ = pumpable_task_runner_->task_queue_.insert(
      pumpable_task_runner_->task_queue_.end(), this);
}

void WrappedTask::RemoveFromTaskRunnerQueue() {
  if (!pumpable_task_runner_.get())
    return;
  // The scope of the task runner's lock must be limited because removing
  // this reference to the task runner may destroy it.
  {
    base::AutoLock lock(pumpable_task_runner_->task_queue_lock_);
    pumpable_task_runner_->task_queue_.erase(iterator_);
    iterator_ = pumpable_task_runner_->task_queue_.end();
  }
  pumpable_task_runner_.reset();
}

////////////////////////////////////////////////////////////////////////////////
// PumpableTaskRunner

PumpableTaskRunner::PumpableTaskRunner(
    const EventTimedWaitCallback& event_timed_wait_callback,
    const scoped_refptr<base::SingleThreadTaskRunner>& target_task_runner)
    : event_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
             base::WaitableEvent::InitialState::NOT_SIGNALED),
      event_timed_wait_callback_(event_timed_wait_callback),
      target_task_runner_(target_task_runner) {}

PumpableTaskRunner::~PumpableTaskRunner() {
  // Because tasks hold a reference to the task runner, the task queue must
  // be empty when it is destroyed.
  DCHECK(task_queue_.empty());
}

bool PumpableTaskRunner::WaitForSingleWrappedTaskToRun(
    const base::TimeDelta& max_delay) {
  base::TimeTicks stop_waiting_time = base::TimeTicks::Now() + max_delay;

  for (;;) {
    base::TimeTicks current_time = base::TimeTicks::Now();
    base::TimeTicks next_task_time = stop_waiting_time;

    // Find the first task to execute in the list. This lookup takes O(n) time,
    // but n is rarely more than 2, and has never been observed to be more than
    // 12.
    WrappedTask* task_to_execute = NULL;
    {
      base::AutoLock lock(task_queue_lock_);

      for (WrappedTaskQueue::iterator it = task_queue_.begin();
           it != task_queue_.end(); ++it) {
        WrappedTask* potential_task = *it;

        // If this task is scheduled for the future, take it into account when
        // deciding how long to sleep, and continue on to the next task.
        if (potential_task->can_run_time() > current_time) {
          if (potential_task->can_run_time() < next_task_time)
            next_task_time = potential_task->can_run_time();
          continue;
        }
        // If there is a better candidate than this task, continue to the next
        // task.
        if (task_to_execute &&
            task_to_execute->ShouldRunBefore(*potential_task)) {
          continue;
        }
        task_to_execute = potential_task;
      }
    }

    if (task_to_execute) {
      task_to_execute->Run();
      return true;
    }

    // Calculate how much time we have left before we have to stop waiting or
    // until a currently-enqueued task will be ready to run.
    base::TimeDelta max_sleep_time = next_task_time - current_time;
    if (max_sleep_time <= base::Milliseconds(0))
      break;

    event_timed_wait_callback_.Run(&event_, max_sleep_time);
  }

  return false;
}

bool PumpableTaskRunner::EnqueueAndPostWrappedTask(
    const base::Location& from_here,
    std::unique_ptr<WrappedTask> task,
    base::TimeDelta delay) {
  task->AddToTaskRunnerQueue(this);

  // Notify anyone waiting on the UI thread that there is a new entry in the
  // task map.  If they don't find the entry they are looking for, then they
  // will just continue waiting.
  event_.Signal();

  return target_task_runner_->PostDelayedTask(
      from_here, base::BindOnce(&WrappedTask::Run, std::move(task)), delay);
}

////////////////////////////////////////////////////////////////////////////////
// PumpableTaskRunner, base::SingleThreadTaskRunner implementation:

bool PumpableTaskRunner::PostDelayedTask(const base::Location& from_here,
                                         base::OnceClosure task,
                                         base::TimeDelta delay) {
  return EnqueueAndPostWrappedTask(
      from_here, std::make_unique<WrappedTask>(std::move(task), delay), delay);
}

bool PumpableTaskRunner::PostNonNestableDelayedTask(
    const base::Location& from_here,
    base::OnceClosure task,
    base::TimeDelta delay) {
  // The correctness of non-nestable events hasn't been proven for this
  // structure.
  NOTREACHED_IN_MIGRATION();
  return false;
}

bool PumpableTaskRunner::RunsTasksInCurrentSequence() const {
  return target_task_runner_->RunsTasksInCurrentSequence();
}

}  // namespace

////////////////////////////////////////////////////////////////////////////////
// WindowResizeHelperMac

scoped_refptr<base::SingleThreadTaskRunner> WindowResizeHelperMac::task_runner()
    const {
  return task_runner_;
}

// static
WindowResizeHelperMac* WindowResizeHelperMac::Get() {
  return g_window_resize_helper.Pointer();
}

void WindowResizeHelperMac::Init(
    const scoped_refptr<base::SingleThreadTaskRunner>& target_task_runner) {
  DCHECK(!task_runner_);
  task_runner_ = new PumpableTaskRunner(
      base::BindRepeating(&WindowResizeHelperMac::EventTimedWait),
      target_task_runner);
}

void WindowResizeHelperMac::ShutdownForTests() {
  task_runner_ = nullptr;
}

bool WindowResizeHelperMac::WaitForSingleTaskToRun(
    const base::TimeDelta& max_delay) {
  PumpableTaskRunner* pumpable_task_runner =
      static_cast<PumpableTaskRunner*>(task_runner_.get());
  if (!pumpable_task_runner)
    return false;
  return pumpable_task_runner->WaitForSingleWrappedTaskToRun(max_delay);
}

WindowResizeHelperMac::WindowResizeHelperMac() {}
WindowResizeHelperMac::~WindowResizeHelperMac() {}

void WindowResizeHelperMac::EventTimedWait(base::WaitableEvent* event,
                                           base::TimeDelta delay) {
  // http://crbug.com/902829
  base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope allow_wait;
  event->TimedWait(delay);
}

}  // namespace ui