blob: b64e09b01d1f6cff4fe155e37a7b0c45ad40115f [file] [log] [blame]
// Copyright 2015 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 "ui/accelerated_widget_mac/window_resize_helper_mac.h"
#include <stdint.h>
#include <list>
#include "base/bind.h"
#include "base/callback.h"
#include "base/macros.h"
#include "base/single_thread_task_runner.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread_restrictions.h"
namespace ui {
namespace {
class WrappedTask;
class PumpableTaskRunner;
typedef std::list<WrappedTask*> WrappedTaskQueue;
typedef base::Callback<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 to wrap them in this structure and track whether or not they have run
// yet, to avoid running them twice.
class WrappedTask {
WrappedTask(const base::Closure& closure, base::TimeDelta delay);
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_; }
base::Closure 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 {
const EventTimedWaitCallback& event_timed_wait_callback,
const scoped_refptr<base::SingleThreadTaskRunner>& target_task_runner);
// Enqueue WrappedTask and post it to |target_task_runner_|.
bool EnqueueAndPostWrappedTask(const tracked_objects::Location& from_here,
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 tracked_objects::Location& from_here,
const base::Closure& task,
base::TimeDelta delay) override;
bool PostNonNestableDelayedTask(const tracked_objects::Location& from_here,
const base::Closure& task,
base::TimeDelta delay) override;
bool RunsTasksOnCurrentThread() const override;
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 =
// WrappedTask
WrappedTask::WrappedTask(const base::Closure& closure, base::TimeDelta delay)
: closure_(closure),
can_run_time_(base::TimeTicks::Now() + delay),
sequence_number_(0) {}
WrappedTask::~WrappedTask() {
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.
return false;
void WrappedTask::Run() {
if (has_run_)
has_run_ = true;
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())
// 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_);
iterator_ = pumpable_task_runner_->task_queue_.end();
pumpable_task_runner_ = NULL;
// PumpableTaskRunner
const EventTimedWaitCallback& event_timed_wait_callback,
const scoped_refptr<base::SingleThreadTaskRunner>& target_task_runner)
: event_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
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.
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();
// If there is a better candidate than this task, continue to the next
// task.
if (task_to_execute &&
task_to_execute->ShouldRunBefore(*potential_task)) {
task_to_execute = potential_task;
if (task_to_execute) {
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::TimeDelta::FromMilliseconds(0))
event_timed_wait_callback_.Run(&event_, max_sleep_time);
return false;
bool PumpableTaskRunner::EnqueueAndPostWrappedTask(
const tracked_objects::Location& from_here,
WrappedTask* task,
base::TimeDelta delay) {
// 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.
return target_task_runner_->PostDelayedTask(
from_here, base::Bind(&WrappedTask::Run, base::Owned(task)), delay);
// PumpableTaskRunner, base::SingleThreadTaskRunner implementation:
bool PumpableTaskRunner::PostDelayedTask(
const tracked_objects::Location& from_here,
const base::Closure& task,
base::TimeDelta delay) {
return EnqueueAndPostWrappedTask(from_here, new WrappedTask(task, delay),
bool PumpableTaskRunner::PostNonNestableDelayedTask(
const tracked_objects::Location& from_here,
const base::Closure& task,
base::TimeDelta delay) {
// The correctness of non-nestable events hasn't been proven for this
// structure.
return false;
bool PumpableTaskRunner::RunsTasksOnCurrentThread() const {
return target_task_runner_->RunsTasksOnCurrentThread();
} // 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) {
task_runner_ = new PumpableTaskRunner(
base::Bind(&WindowResizeHelperMac::EventTimedWait), target_task_runner);
void WindowResizeHelperMac::ShutdownForTests() {
task_runner_ = nullptr;
bool WindowResizeHelperMac::WaitForSingleTaskToRun(
const base::TimeDelta& max_delay) {
PumpableTaskRunner* pumpable_task_runner =
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) {
base::ThreadRestrictions::ScopedAllowWait allow_wait;
} // namespace ui