Reland "Add MOCK_TIME mode to ScopedTaskEnvironment :)"

Reland follow-up nits CL along with it this time
(https://chromium-review.googlesource.com/c/chromium/src/+/783873)

Initial CL made ProfileShortcutManagerTest flaky.

Running them locally it turns out they mostly pass but they spew out
a whole lot of "ScopedTaskEnvironment::RunUntilIdle() appears to be
stuck in infinite loop" warning logs (which were added in this CL).
This is because the ongoing tasks that prevent DisallowRunTasks()
are slow and the main thread goes into a busy loop trying to
DisallowRunTasks(). These tests being already slow, these prints
pushed them over the edge (I think).

DisallowRunTasks() was tweaked in this reland to not return until
either one task completes or a short timeout expires (see code
comments for detailed reasoning).

This also allows removing the YieldCurrentThread() logic in the original
CL as busy-looping is avoided altogether.

The LOG statement trying to catch infinite RunUntilIdle() was also
removed as it wasn't really the nice place for it (it should a counter
in the RunOrSkipTask() override if we want it later).

This is a reland of 5e2df665dfce3a3aceb4852d90ad55d31fc36f20
Original change's description:
> Add MOCK_TIME mode to ScopedTaskEnvironment :)
>
> Taking advantage of the new kBoundToThread mode on
> TestMockTimeTaskRunner.
>
> This change also required tweaking the
> ScopedTaskEnvironment::RunUntilIdle() logic as RunLoop().Run() on
> TestMockTimeTaskRunner results in advancing time when there's no
> request to quit-when-idle which is undesired here. New logic gets rid
> of need for |on_queue_empty_closure_| and I think is simpler overall.
> As of patch set 20, this new RunUntilIdle() logic also avoids using
> TaskScheduler::FlushForTesting() as that can result in hangs should a
> TaskScheduler task synchronously block on the main thread.
>
> R=fdoray@chromium.org
> TBR=gab@chromium.org (IWYU fixes)
>
> Bug: 708584
> Change-Id: I76ba55ec64d398151420379d3fcdcd5186fbceb8
> Reviewed-on: https://chromium-review.googlesource.com/638550
> Commit-Queue: Gabriel Charette <gab@chromium.org>
> Reviewed-by: Gabriel Charette <gab@chromium.org>
> Reviewed-by: Kyle Horimoto <khorimoto@chromium.org>
> Reviewed-by: François Doray <fdoray@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#518433}

TEST=Repro'ed https://crbug.com/787683 locally and fixed.
TBR=gab@chromium.org (IWYU fixes)

Bug: 708584, 787683
Change-Id: I511c159ccbb608aa54394a4f35c40ad14697196b
Reviewed-on: https://chromium-review.googlesource.com/788133
Commit-Queue: Gabriel Charette <gab@chromium.org>
Reviewed-by: François Doray <fdoray@chromium.org>
Reviewed-by: Gabriel Charette <gab@chromium.org>
Cr-Commit-Position: refs/heads/master@{#519032}

CrOS-Libchrome-Original-Commit: d4723a3dfd0215a72507d10ce0bd0e75f5f76bb6
diff --git a/base/observer_list_unittest.cc b/base/observer_list_unittest.cc
index aa68bb1..fa6ab78 100644
--- a/base/observer_list_unittest.cc
+++ b/base/observer_list_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/compiler_specific.h"
 #include "base/location.h"
 #include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "base/sequenced_task_runner.h"
 #include "base/single_thread_task_runner.h"
diff --git a/base/task_scheduler/task_tracker.cc b/base/task_scheduler/task_tracker.cc
index 3d3c088..c5be7d2 100644
--- a/base/task_scheduler/task_tracker.cc
+++ b/base/task_scheduler/task_tracker.cc
@@ -246,7 +246,7 @@
 
 void TaskTracker::Flush() {
   AutoSchedulerLock auto_lock(flush_lock_);
-  while (subtle::Acquire_Load(&num_pending_undelayed_tasks_) != 0 &&
+  while (subtle::Acquire_Load(&num_incomplete_undelayed_tasks_) != 0 &&
          !IsShutdownComplete()) {
     flush_cv_->Wait();
   }
@@ -259,7 +259,7 @@
     return false;
 
   if (task->delayed_run_time.is_null())
-    subtle::NoBarrier_AtomicIncrement(&num_pending_undelayed_tasks_, 1);
+    subtle::NoBarrier_AtomicIncrement(&num_incomplete_undelayed_tasks_, 1);
 
   debug::TaskAnnotator task_annotator;
   task_annotator.DidQueueTask(kQueueFunctionName, *task);
@@ -313,9 +313,7 @@
     AfterRunTask(shutdown_behavior);
 
   if (!is_delayed)
-    DecrementNumPendingUndelayedTasks();
-
-  OnRunNextTaskCompleted();
+    DecrementNumIncompleteUndelayedTasks();
 
   const bool sequence_is_empty_after_pop = sequence->Pop();
 
@@ -476,8 +474,8 @@
 }
 #endif
 
-int TaskTracker::GetNumPendingUndelayedTasksForTesting() const {
-  return subtle::NoBarrier_Load(&num_pending_undelayed_tasks_);
+int TaskTracker::GetNumIncompleteUndelayedTasksForTesting() const {
+  return subtle::NoBarrier_Load(&num_incomplete_undelayed_tasks_);
 }
 
 bool TaskTracker::BeforePostTask(TaskShutdownBehavior shutdown_behavior) {
@@ -595,11 +593,11 @@
   shutdown_event_->Signal();
 }
 
-void TaskTracker::DecrementNumPendingUndelayedTasks() {
-  const auto new_num_pending_undelayed_tasks =
-      subtle::Barrier_AtomicIncrement(&num_pending_undelayed_tasks_, -1);
-  DCHECK_GE(new_num_pending_undelayed_tasks, 0);
-  if (new_num_pending_undelayed_tasks == 0) {
+void TaskTracker::DecrementNumIncompleteUndelayedTasks() {
+  const auto new_num_incomplete_undelayed_tasks =
+      subtle::Barrier_AtomicIncrement(&num_incomplete_undelayed_tasks_, -1);
+  DCHECK_GE(new_num_incomplete_undelayed_tasks, 0);
+  if (new_num_incomplete_undelayed_tasks == 0) {
     AutoSchedulerLock auto_lock(flush_lock_);
     flush_cv_->Signal();
   }
diff --git a/base/task_scheduler/task_tracker.h b/base/task_scheduler/task_tracker.h
index 37e7f89..f249b73 100644
--- a/base/task_scheduler/task_tracker.h
+++ b/base/task_scheduler/task_tracker.h
@@ -99,7 +99,7 @@
   // This can only be called once.
   void Shutdown();
 
-  // Waits until there are no pending undelayed tasks. May be called in tests
+  // Waits until there are no incomplete undelayed tasks. May be called in tests
   // to validate that a condition is met after all undelayed tasks have run.
   //
   // Does not wait for delayed tasks. Waits for undelayed tasks posted from
@@ -166,13 +166,9 @@
   virtual bool IsPostingBlockShutdownTaskAfterShutdownAllowed();
 #endif
 
-  // Called at the very end of RunNextTask() after the completion of all task
-  // metrics accounting.
-  virtual void OnRunNextTaskCompleted() {}
-
   // Returns the number of undelayed tasks that haven't completed their
-  // execution.
-  int GetNumPendingUndelayedTasksForTesting() const;
+  // execution (still queued or in progress).
+  int GetNumIncompleteUndelayedTasksForTesting() const;
 
  private:
   class State;
@@ -199,9 +195,9 @@
   // shutdown has started.
   void OnBlockingShutdownTasksComplete();
 
-  // Decrements the number of pending undelayed tasks and signals |flush_cv_| if
-  // it reaches zero.
-  void DecrementNumPendingUndelayedTasks();
+  // Decrements the number of incomplete undelayed tasks and signals |flush_cv_|
+  // if it reaches zero.
+  void DecrementNumIncompleteUndelayedTasks();
 
   // To be called after running a background task from |just_ran_sequence|.
   // Performs the following actions:
@@ -233,15 +229,15 @@
   // decremented with a memory barrier after a task runs. Is accessed with an
   // acquire memory barrier in Flush(). The memory barriers ensure that the
   // memory written by flushed tasks is visible when Flush() returns.
-  subtle::Atomic32 num_pending_undelayed_tasks_ = 0;
+  subtle::Atomic32 num_incomplete_undelayed_tasks_ = 0;
 
   // Lock associated with |flush_cv_|. Partially synchronizes access to
-  // |num_pending_undelayed_tasks_|. Full synchronization isn't needed because
-  // it's atomic, but synchronization is needed to coordinate waking and
+  // |num_incomplete_undelayed_tasks_|. Full synchronization isn't needed
+  // because it's atomic, but synchronization is needed to coordinate waking and
   // sleeping at the right time.
   mutable SchedulerLock flush_lock_;
 
-  // Signaled when |num_pending_undelayed_tasks_| is zero or when shutdown
+  // Signaled when |num_incomplete_undelayed_tasks_| is zero or when shutdown
   // completes.
   const std::unique_ptr<ConditionVariable> flush_cv_;
 
diff --git a/base/test/scoped_task_environment.cc b/base/test/scoped_task_environment.cc
index 9ec592c..2b80000 100644
--- a/base/test/scoped_task_environment.cc
+++ b/base/test/scoped_task_environment.cc
@@ -6,12 +6,14 @@
 
 #include "base/bind_helpers.h"
 #include "base/logging.h"
+#include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "base/synchronization/condition_variable.h"
 #include "base/synchronization/lock.h"
 #include "base/task_scheduler/post_task.h"
 #include "base/task_scheduler/task_scheduler.h"
 #include "base/task_scheduler/task_scheduler_impl.h"
+#include "base/test/test_mock_time_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 
@@ -20,23 +22,21 @@
 
 namespace {
 
-class TaskObserver : public MessageLoop::TaskObserver {
- public:
-  TaskObserver() = default;
-
-  // MessageLoop::TaskObserver:
-  void WillProcessTask(const PendingTask& pending_task) override {}
-  void DidProcessTask(const PendingTask& pending_task) override {
-    ++task_count_;
+std::unique_ptr<MessageLoop> CreateMessageLoopForMainThreadType(
+    ScopedTaskEnvironment::MainThreadType main_thread_type) {
+  switch (main_thread_type) {
+    case ScopedTaskEnvironment::MainThreadType::DEFAULT:
+      return std::make_unique<MessageLoop>(MessageLoop::TYPE_DEFAULT);
+    case ScopedTaskEnvironment::MainThreadType::MOCK_TIME:
+      return nullptr;
+    case ScopedTaskEnvironment::MainThreadType::UI:
+      return std::make_unique<MessageLoop>(MessageLoop::TYPE_UI);
+    case ScopedTaskEnvironment::MainThreadType::IO:
+      return std::make_unique<MessageLoop>(MessageLoop::TYPE_IO);
   }
-
-  int task_count() const { return task_count_; }
-
- private:
-  int task_count_ = 0;
-
-  DISALLOW_COPY_AND_ASSIGN(TaskObserver);
-};
+  NOTREACHED();
+  return nullptr;
+}
 
 }  // namespace
 
@@ -45,15 +45,17 @@
  public:
   TestTaskTracker();
 
-  void RegisterOnQueueEmptyClosure(OnceClosure queue_empty_closure);
-
-  // Returns true if closure needed reset.
-  bool ResetOnQueueEmptyClosureIfNotNull();
-
   // Allow running tasks.
-  void AllowRunRask();
+  void AllowRunTasks();
 
-  // Disallow running tasks. No-ops and returns false if a task is running.
+  // Disallow running tasks. Returns true on success; success requires there to
+  // be no tasks currently running. Returns false if >0 tasks are currently
+  // running. Prior to returning false, it will attempt to block until at least
+  // one task has completed (in an attempt to avoid callers busy-looping
+  // DisallowRunTasks() calls with the same set of slowly ongoing tasks). This
+  // block attempt will also have a short timeout (in an attempt to prevent the
+  // fallout of blocking: if the only task remaining is blocked on the main
+  // thread, waiting for it to complete results in a deadlock...).
   bool DisallowRunTasks();
 
  private:
@@ -63,20 +65,19 @@
   void RunOrSkipTask(std::unique_ptr<internal::Task> task,
                      internal::Sequence* sequence,
                      bool can_run_task) override;
-  void OnRunNextTaskCompleted() override;
 
   // Synchronizes accesses to members below.
   Lock lock_;
 
-  // Closure posted to the main thread when the task queue becomes empty.
-  OnceClosure queue_empty_closure_;
-
   // True if running tasks is allowed.
   bool can_run_tasks_ = true;
 
   // Signaled when |can_run_tasks_| becomes true.
   ConditionVariable can_run_tasks_cv_;
 
+  // Signaled when a task is completed.
+  ConditionVariable task_completed_;
+
   // Number of tasks that are currently running.
   int num_tasks_running_ = 0;
 
@@ -87,11 +88,12 @@
     MainThreadType main_thread_type,
     ExecutionMode execution_control_mode)
     : execution_control_mode_(execution_control_mode),
-      message_loop_(main_thread_type == MainThreadType::DEFAULT
-                        ? MessageLoop::TYPE_DEFAULT
-                        : (main_thread_type == MainThreadType::UI
-                               ? MessageLoop::TYPE_UI
-                               : MessageLoop::TYPE_IO)),
+      message_loop_(CreateMessageLoopForMainThreadType(main_thread_type)),
+      mock_time_task_runner_(
+          main_thread_type == MainThreadType::MOCK_TIME
+              ? MakeRefCounted<TestMockTimeTaskRunner>(
+                    TestMockTimeTaskRunner::Type::kBoundToThread)
+              : nullptr),
       task_tracker_(new TestTaskTracker()) {
   CHECK(!TaskScheduler::GetInstance());
 
@@ -125,7 +127,7 @@
   CHECK_EQ(TaskScheduler::GetInstance(), task_scheduler_);
   // Without FlushForTesting(), DeleteSoon() and ReleaseSoon() tasks could be
   // skipped, resulting in memory leaks.
-  task_tracker_->AllowRunRask();
+  task_tracker_->AllowRunTasks();
   TaskScheduler::GetInstance()->FlushForTesting();
   TaskScheduler::GetInstance()->Shutdown();
   TaskScheduler::GetInstance()->JoinForTesting();
@@ -134,79 +136,106 @@
 
 scoped_refptr<base::SingleThreadTaskRunner>
 ScopedTaskEnvironment::GetMainThreadTaskRunner() {
-  return message_loop_.task_runner();
+  if (message_loop_)
+    return message_loop_->task_runner();
+  DCHECK(mock_time_task_runner_);
+  return mock_time_task_runner_;
 }
 
 void ScopedTaskEnvironment::RunUntilIdle() {
+  // TODO(gab): This can be heavily simplified to essentially:
+  //     bool HasMainThreadTasks() {
+  //      if (message_loop_)
+  //        return !message_loop_->IsIdleForTesting();
+  //      return mock_time_task_runner_->NextPendingTaskDelay().is_zero();
+  //     }
+  //     while (task_tracker_->HasIncompleteTasks() || HasMainThreadTasks()) {
+  //       base::RunLoop().RunUntilIdle();
+  //       // Avoid busy-looping.
+  //       if (task_tracker_->HasIncompleteTasks())
+  //         PlatformThread::Sleep(TimeDelta::FromMilliSeconds(1));
+  //     }
+  // Challenge: HasMainThreadTasks() requires support for proper
+  // IncomingTaskQueue::IsIdleForTesting() (check all queues).
+  //
+  // Other than that it works because once |task_tracker_->HasIncompleteTasks()|
+  // is false we know for sure that the only thing that can make it true is a
+  // main thread task (ScopedTaskEnvironment owns all the threads). As such we
+  // can't racily see it as false on the main thread and be wrong as if it the
+  // main thread sees the atomic count at zero, it's the only one that can make
+  // it go up. And the only thing that can make it go up on the main thread are
+  // main thread tasks and therefore we're done if there aren't any left.
+  //
+  // This simplification further allows simplification of DisallowRunTasks().
+  //
+  // This can also be simplified even further once TaskTracker becomes directly
+  // aware of main thread tasks. https://crbug.com/660078.
+
   for (;;) {
-    RunLoop run_loop;
+    task_tracker_->AllowRunTasks();
 
-    // Register a closure to stop running tasks on the main thread when the
-    // TaskScheduler queue becomes empty.
-    task_tracker_->RegisterOnQueueEmptyClosure(run_loop.QuitWhenIdleClosure());
+    // First run as many tasks as possible on the main thread in parallel with
+    // tasks in TaskScheduler. This increases likelihood of TSAN catching
+    // threading errors and eliminates possibility of hangs should a
+    // TaskScheduler task synchronously block on a main thread task
+    // (TaskScheduler::FlushForTesting() can't be used here for that reason).
+    RunLoop().RunUntilIdle();
 
-    // The closure registered above may never run if the TaskScheduler queue
-    // starts empty. Post a TaskScheduler tasks to make sure that the queue
-    // doesn't start empty.
-    PostTask(FROM_HERE, BindOnce(&DoNothing));
-
-    // Run main thread and TaskScheduler tasks.
-    task_tracker_->AllowRunRask();
-    TaskObserver task_observer;
-    MessageLoop::current()->AddTaskObserver(&task_observer);
-    run_loop.Run();
-    MessageLoop::current()->RemoveTaskObserver(&task_observer);
-
-    // If |task_tracker_|'s |queue_empty_closure_| is not null, it means that
-    // external code exited the RunLoop (through deprecated static methods) and
-    // that the MessageLoop and TaskScheduler queues might not be empty. Run the
-    // loop again to make sure that no task remains.
-    if (task_tracker_->ResetOnQueueEmptyClosureIfNotNull())
+    // Then halt TaskScheduler. DisallowRunTasks() failing indicates that there
+    // were TaskScheduler tasks currently running. In that case, try again from
+    // top when DisallowRunTasks() yields control back to this thread as they
+    // may have posted main thread tasks.
+    if (!task_tracker_->DisallowRunTasks())
       continue;
 
-    // If tasks other than the QuitWhenIdle closure ran on the main thread, they
-    // may have posted TaskScheduler tasks that didn't run yet. Another
-    // iteration is required to run them.
-    //
-    // If the ExecutionMode is QUEUED and DisallowRunTasks() fails,
-    // another iteration is required to make sure that RunUntilIdle() doesn't
-    // return while TaskScheduler tasks are still allowed to run.
-    //
-    // Note: DisallowRunTasks() fails when a TaskScheduler task is running. A
-    // TaskScheduler task may be running after the TaskScheduler queue became
-    // empty even if no tasks ran on the main thread in these cases:
-    // - An undelayed task became ripe for execution.
-    // - A task was posted from an external thread.
-    if (task_observer.task_count() == 1 &&
-        (execution_control_mode_ != ExecutionMode::QUEUED ||
-         task_tracker_->DisallowRunTasks())) {
+    // Once TaskScheduler is halted. Run any remaining main thread tasks (which
+    // may have been posted by TaskScheduler tasks that completed between the
+    // above main thread RunUntilIdle() and TaskScheduler DisallowRunTasks()).
+    // Note: this assumes that no main thread task synchronously blocks on a
+    // TaskScheduler tasks (it certainly shouldn't); this call could otherwise
+    // hang.
+    RunLoop().RunUntilIdle();
+
+    // The above RunUntilIdle() guarantees there are no remaining main thread
+    // tasks (the TaskScheduler being halted during the last RunUntilIdle() is
+    // key as it prevents a task being posted to it racily with it determining
+    // it had no work remaining). Therefore, we're done if there is no more work
+    // on TaskScheduler either (there can be TaskScheduler work remaining if
+    // DisallowRunTasks() preempted work and/or the last RunUntilIdle() posted
+    // more TaskScheduler tasks).
+    // Note: this last |if| couldn't be turned into a |do {} while();|. A
+    // conditional loop makes it such that |continue;| results in checking the
+    // condition (not unconditionally loop again) which would be incorrect for
+    // the above logic as it'd then be possible for a TaskScheduler task to be
+    // running during the DisallowRunTasks() test, causing it to fail, but then
+    // post to the main thread and complete before the loop's condition is
+    // verified which could result in GetNumIncompleteUndelayedTasksForTesting()
+    // returning 0 and the loop erroneously exiting with a pending task on the
+    // main thread.
+    if (task_tracker_->GetNumIncompleteUndelayedTasksForTesting() == 0)
       break;
-    }
   }
+
+  // The above loop always ends with running tasks being disallowed. Re-enable
+  // parallel execution before returning unless in ExecutionMode::QUEUED.
+  if (execution_control_mode_ != ExecutionMode::QUEUED)
+    task_tracker_->AllowRunTasks();
+}
+
+void ScopedTaskEnvironment::FastForwardBy(TimeDelta delta) {
+  DCHECK(mock_time_task_runner_);
+  mock_time_task_runner_->FastForwardBy(delta);
+}
+
+void ScopedTaskEnvironment::FastForwardUntilNoTasksRemain() {
+  DCHECK(mock_time_task_runner_);
+  mock_time_task_runner_->FastForwardUntilNoTasksRemain();
 }
 
 ScopedTaskEnvironment::TestTaskTracker::TestTaskTracker()
-    : can_run_tasks_cv_(&lock_) {}
+    : can_run_tasks_cv_(&lock_), task_completed_(&lock_) {}
 
-void ScopedTaskEnvironment::TestTaskTracker::RegisterOnQueueEmptyClosure(
-    OnceClosure queue_empty_closure) {
-  AutoLock auto_lock(lock_);
-  CHECK(!queue_empty_closure_);
-  queue_empty_closure_ = std::move(queue_empty_closure);
-}
-
-bool ScopedTaskEnvironment::TestTaskTracker::
-    ResetOnQueueEmptyClosureIfNotNull() {
-  AutoLock auto_lock(lock_);
-  if (queue_empty_closure_) {
-    queue_empty_closure_ = Closure();
-    return true;
-  }
-
-  return false;
-}
-
-void ScopedTaskEnvironment::TestTaskTracker::AllowRunRask() {
+void ScopedTaskEnvironment::TestTaskTracker::AllowRunTasks() {
   AutoLock auto_lock(lock_);
   can_run_tasks_ = true;
   can_run_tasks_cv_.Broadcast();
@@ -216,8 +245,13 @@
   AutoLock auto_lock(lock_);
 
   // Can't disallow run task if there are tasks running.
-  if (num_tasks_running_ > 0)
+  if (num_tasks_running_ > 0) {
+    // Attempt to wait a bit so that the caller doesn't busy-loop with the same
+    // set of pending work. A short wait is required to avoid deadlock
+    // scenarios. See DisallowRunTasks()'s declaration for more details.
+    task_completed_.TimedWait(TimeDelta::FromMilliseconds(1));
     return false;
+  }
 
   can_run_tasks_ = false;
   return true;
@@ -246,15 +280,8 @@
     CHECK(can_run_tasks_);
 
     --num_tasks_running_;
-  }
-}
 
-void ScopedTaskEnvironment::TestTaskTracker::OnRunNextTaskCompleted() {
-  // Notify the main thread when no tasks are running or queued.
-  AutoLock auto_lock(lock_);
-  if (num_tasks_running_ == 0 && GetNumPendingUndelayedTasksForTesting() == 0 &&
-      queue_empty_closure_) {
-    std::move(queue_empty_closure_).Run();
+    task_completed_.Broadcast();
   }
 }
 
diff --git a/base/test/scoped_task_environment.h b/base/test/scoped_task_environment.h
index 4e73085..45b2531 100644
--- a/base/test/scoped_task_environment.h
+++ b/base/test/scoped_task_environment.h
@@ -6,13 +6,15 @@
 #define BASE_TEST_SCOPED_TASK_ENVIRONMENT_H_
 
 #include "base/macros.h"
-#include "base/message_loop/message_loop.h"
+#include "base/memory/ref_counted.h"
 #include "base/single_thread_task_runner.h"
 #include "base/task_scheduler/lazy_task_runner.h"
 
 namespace base {
 
+class MessageLoop;
 class TaskScheduler;
+class TestMockTimeTaskRunner;
 
 namespace test {
 
@@ -58,6 +60,12 @@
   enum class MainThreadType {
     // The main thread doesn't pump system messages.
     DEFAULT,
+    // The main thread doesn't pump system messages and uses a mock clock for
+    // delayed tasks (controllable via FastForward*() methods).
+    // TODO(gab): Make this the default |main_thread_type|.
+    // TODO(gab): Also mock the TaskScheduler's clock simultaneously (this
+    // currently only mocks the main thread's clock).
+    MOCK_TIME,
     // The main thread pumps UI messages.
     UI,
     // The main thread pumps asynchronous IO messages.
@@ -85,18 +93,32 @@
   scoped_refptr<base::SingleThreadTaskRunner> GetMainThreadTaskRunner();
 
   // Runs tasks until both the (Thread|Sequenced)TaskRunnerHandle and the
-  // TaskScheduler queues are empty.
+  // TaskScheduler's non-delayed queues are empty.
   void RunUntilIdle();
 
+  // Only valid for instances with a MOCK_TIME MainThreadType. Fast-forwards
+  // virtual time by |delta|, causing all tasks on the main thread with a
+  // remaining delay less than or equal to |delta| to be executed before this
+  // returns. |delta| must be non-negative.
+  // TODO(gab): Make this apply to TaskScheduler delayed tasks as well
+  // (currently only main thread time is mocked).
+  void FastForwardBy(TimeDelta delta);
+
+  // Only valid for instances with a MOCK_TIME MainThreadType.
+  // Short for FastForwardBy(TimeDelta::Max()).
+  void FastForwardUntilNoTasksRemain();
+
  private:
   class TestTaskTracker;
 
   const ExecutionMode execution_control_mode_;
 
-  // Note: |message_loop_| is an implementation detail and will be replaced in
-  // the future, do NOT rely on the presence of a MessageLoop beyond
-  // (Thread|Sequenced)TaskRunnerHandle and RunLoop.
-  MessageLoop message_loop_;
+  // Exactly one of these will be non-null to provide the task environment on
+  // the main thread. Users of this class should NOT rely on the presence of a
+  // MessageLoop beyond (Thread|Sequenced)TaskRunnerHandle and RunLoop as
+  // the backing implementation of each MainThreadType may change over time.
+  const std::unique_ptr<MessageLoop> message_loop_;
+  const scoped_refptr<TestMockTimeTaskRunner> mock_time_task_runner_;
 
   const TaskScheduler* task_scheduler_ = nullptr;
 
diff --git a/base/test/scoped_task_environment_unittest.cc b/base/test/scoped_task_environment_unittest.cc
index 93a980a..9b29f9c 100644
--- a/base/test/scoped_task_environment_unittest.cc
+++ b/base/test/scoped_task_environment_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "base/test/scoped_task_environment.h"
 
+#include "base/atomicops.h"
 #include "base/bind.h"
 #include "base/synchronization/atomic_flag.h"
 #include "base/synchronization/waitable_event.h"
@@ -18,6 +19,9 @@
 
 namespace {
 
+class ScopedTaskEnvironmentTest
+    : public testing::TestWithParam<ScopedTaskEnvironment::MainThreadType> {};
+
 void VerifyRunUntilIdleDidNotReturnAndSetFlag(
     AtomicFlag* run_until_idle_returned,
     AtomicFlag* task_ran) {
@@ -26,10 +30,11 @@
 }
 
 void RunUntilIdleTest(
+    ScopedTaskEnvironment::MainThreadType main_thread_type,
     ScopedTaskEnvironment::ExecutionMode execution_control_mode) {
   AtomicFlag run_until_idle_returned;
-  ScopedTaskEnvironment scoped_task_environment(
-      ScopedTaskEnvironment::MainThreadType::DEFAULT, execution_control_mode);
+  ScopedTaskEnvironment scoped_task_environment(main_thread_type,
+                                                execution_control_mode);
 
   AtomicFlag first_main_thread_task_ran;
   ThreadTaskRunnerHandle::Get()->PostTask(
@@ -63,20 +68,19 @@
 
 }  // namespace
 
-TEST(ScopedTaskEnvironmentTest, QueuedRunUntilIdle) {
-  RunUntilIdleTest(ScopedTaskEnvironment::ExecutionMode::QUEUED);
+TEST_P(ScopedTaskEnvironmentTest, QueuedRunUntilIdle) {
+  RunUntilIdleTest(GetParam(), ScopedTaskEnvironment::ExecutionMode::QUEUED);
 }
 
-TEST(ScopedTaskEnvironmentTest, AsyncRunUntilIdle) {
-  RunUntilIdleTest(ScopedTaskEnvironment::ExecutionMode::ASYNC);
+TEST_P(ScopedTaskEnvironmentTest, AsyncRunUntilIdle) {
+  RunUntilIdleTest(GetParam(), ScopedTaskEnvironment::ExecutionMode::ASYNC);
 }
 
 // Verify that tasks posted to an ExecutionMode::QUEUED ScopedTaskEnvironment do
 // not run outside of RunUntilIdle().
-TEST(ScopedTaskEnvironmentTest, QueuedTasksDoNotRunOutsideOfRunUntilIdle) {
+TEST_P(ScopedTaskEnvironmentTest, QueuedTasksDoNotRunOutsideOfRunUntilIdle) {
   ScopedTaskEnvironment scoped_task_environment(
-      ScopedTaskEnvironment::MainThreadType::DEFAULT,
-      ScopedTaskEnvironment::ExecutionMode::QUEUED);
+      GetParam(), ScopedTaskEnvironment::ExecutionMode::QUEUED);
 
   AtomicFlag run_until_idle_called;
   PostTask(FROM_HERE, BindOnce(
@@ -101,10 +105,9 @@
 
 // Verify that a task posted to an ExecutionMode::ASYNC ScopedTaskEnvironment
 // can run without a call to RunUntilIdle().
-TEST(ScopedTaskEnvironmentTest, AsyncTasksRunAsTheyArePosted) {
+TEST_P(ScopedTaskEnvironmentTest, AsyncTasksRunAsTheyArePosted) {
   ScopedTaskEnvironment scoped_task_environment(
-      ScopedTaskEnvironment::MainThreadType::DEFAULT,
-      ScopedTaskEnvironment::ExecutionMode::ASYNC);
+      GetParam(), ScopedTaskEnvironment::ExecutionMode::ASYNC);
 
   WaitableEvent task_ran(WaitableEvent::ResetPolicy::MANUAL,
                          WaitableEvent::InitialState::NOT_SIGNALED);
@@ -117,10 +120,10 @@
 // Verify that a task posted to an ExecutionMode::ASYNC ScopedTaskEnvironment
 // after a call to RunUntilIdle() can run without another call to
 // RunUntilIdle().
-TEST(ScopedTaskEnvironmentTest, AsyncTasksRunAsTheyArePostedAfterRunUntilIdle) {
+TEST_P(ScopedTaskEnvironmentTest,
+       AsyncTasksRunAsTheyArePostedAfterRunUntilIdle) {
   ScopedTaskEnvironment scoped_task_environment(
-      ScopedTaskEnvironment::MainThreadType::DEFAULT,
-      ScopedTaskEnvironment::ExecutionMode::ASYNC);
+      GetParam(), ScopedTaskEnvironment::ExecutionMode::ASYNC);
 
   scoped_task_environment.RunUntilIdle();
 
@@ -132,5 +135,102 @@
   task_ran.Wait();
 }
 
+TEST_P(ScopedTaskEnvironmentTest, DelayedTasks) {
+  ScopedTaskEnvironment scoped_task_environment(
+      GetParam(), ScopedTaskEnvironment::ExecutionMode::ASYNC);
+
+  subtle::Atomic32 counter = 0;
+
+  // Should run only in MOCK_TIME environment when time is fast-forwarded.
+  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE,
+      Bind(
+          [](subtle::Atomic32* counter) {
+            subtle::NoBarrier_AtomicIncrement(counter, 4);
+          },
+          Unretained(&counter)),
+      TimeDelta::FromDays(1));
+  // TODO(gab): This currently doesn't run because the TaskScheduler's clock
+  // isn't mocked but it should be.
+  PostDelayedTask(FROM_HERE,
+                  Bind(
+                      [](subtle::Atomic32* counter) {
+                        subtle::NoBarrier_AtomicIncrement(counter, 128);
+                      },
+                      Unretained(&counter)),
+                  TimeDelta::FromDays(1));
+
+  // Same as first task, longer delays to exercise
+  // FastForwardUntilNoTasksRemain().
+  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE,
+      Bind(
+          [](subtle::Atomic32* counter) {
+            subtle::NoBarrier_AtomicIncrement(counter, 8);
+          },
+          Unretained(&counter)),
+      TimeDelta::FromDays(5));
+  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE,
+      Bind(
+          [](subtle::Atomic32* counter) {
+            subtle::NoBarrier_AtomicIncrement(counter, 16);
+          },
+          Unretained(&counter)),
+      TimeDelta::FromDays(7));
+
+  ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, Bind(
+                     [](subtle::Atomic32* counter) {
+                       subtle::NoBarrier_AtomicIncrement(counter, 1);
+                     },
+                     Unretained(&counter)));
+  PostTask(FROM_HERE, Bind(
+                          [](subtle::Atomic32* counter) {
+                            subtle::NoBarrier_AtomicIncrement(counter, 2);
+                          },
+                          Unretained(&counter)));
+
+  int expected_value = 0;
+  EXPECT_EQ(expected_value, counter);
+
+  // RunUntilIdle() should process non-delayed tasks only in all queues.
+  scoped_task_environment.RunUntilIdle();
+  expected_value += 1;
+  expected_value += 2;
+  EXPECT_EQ(expected_value, counter);
+
+  if (GetParam() == ScopedTaskEnvironment::MainThreadType::MOCK_TIME) {
+    scoped_task_environment.FastForwardBy(TimeDelta::FromSeconds(1));
+    EXPECT_EQ(expected_value, counter);
+
+    scoped_task_environment.FastForwardBy(TimeDelta::FromDays(1));
+    expected_value += 4;
+    EXPECT_EQ(expected_value, counter);
+
+    scoped_task_environment.FastForwardUntilNoTasksRemain();
+    expected_value += 8;
+    expected_value += 16;
+    EXPECT_EQ(expected_value, counter);
+  }
+}
+
+INSTANTIATE_TEST_CASE_P(
+    MainThreadDefault,
+    ScopedTaskEnvironmentTest,
+    ::testing::Values(ScopedTaskEnvironment::MainThreadType::DEFAULT));
+INSTANTIATE_TEST_CASE_P(
+    MainThreadMockTime,
+    ScopedTaskEnvironmentTest,
+    ::testing::Values(ScopedTaskEnvironment::MainThreadType::MOCK_TIME));
+INSTANTIATE_TEST_CASE_P(
+    MainThreadUI,
+    ScopedTaskEnvironmentTest,
+    ::testing::Values(ScopedTaskEnvironment::MainThreadType::UI));
+INSTANTIATE_TEST_CASE_P(
+    MainThreadIO,
+    ScopedTaskEnvironmentTest,
+    ::testing::Values(ScopedTaskEnvironment::MainThreadType::IO));
+
 }  // namespace test
 }  // namespace base