blob: e40a23c292608b148207f4a1f2638a3c984668d8 [file] [log] [blame]
// 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();
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();
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