Make TestMockTimeTaskRunner a RunLoop::Delegate.

Introducing TestMockTimeTaskRunner::Type::kBound which will make that
TestMockTimeTaskRunner takeover the thread it's created on (a la
MessageLoop), enabling RunLoop and Thread/SequencedTaskRunnerHandle.

Also introduces RunLoop::ScopedDisallowRunningForTesting to enforce
mutual exclusion TestMockTimeTaskRunner::ScopedContext (used to toggle
context to another task runner on the main thread) and RunLoop::Run()
(meant to run the current thread's associated task runner). Mixing the
two would result in running the incorrect task runner. While I don't
think this is a use case worth supporting, experience with //base
APIs has taught me that if there's a way to use it wrong, someone
will, and it's much easier to prevent than to heal; hence this check.
(there should already be no RunLoop usage during TaskRunnerHandle 
overrides per RunLoop not being previously supported by 
TestMockTimeTaskRunner and this check ensures it stays that way :))

EDIT: Well except that HeartbeatSenderTest had found a way to use it
the deprecated way, but it's a nice fit for using a kBound 
TestMockTimeTaskRunner so all good :).

Had to drop support for virtual 
TestMockTimeTaskRunner::IsElapsingStopped() which in turn forced
removal of custom task runner in remote_commands_service_unittest.cc
whose use case is now supported by the new API :).

This enables follow-ups to:
 1) Add mock time to base::test::ScopedTaskEnvironment :)
    https://docs.google.com/document/d/1QabRo8c7D9LsYY3cEcaPQbOCLo8Tu-6VLykYXyl3Pkk/edit
 2) Fixing a race in base::Timer which requires RunLoop from
    TestMockTimeTaskRunner to get rid of the two remaining problematic
    use cases (see bug blocked by 703346).

Bug: 703346
Change-Id: I062b77b669853a36c30813e44dd984d01fcefbe2

[email protected] (for components/policy test side-effects)

Change-Id: I062b77b669853a36c30813e44dd984d01fcefbe2
Reviewed-on: https://chromium-review.googlesource.com/614788
Reviewed-by: Gabriel Charette <[email protected]>
Reviewed-by: Dirk Pranke <[email protected]>
Reviewed-by: Scott Nichols <[email protected]>
Reviewed-by: Daniel Cheng <[email protected]>
Commit-Queue: Gabriel Charette <[email protected]>
Cr-Commit-Position: refs/heads/master@{#496111}
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index cae96c3f..10d86ab 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -426,6 +426,14 @@
       ),
       True,
       (),
+    ),
+    (
+      'base::ScopedMockTimeMessageLoopTaskRunner',
+      (
+        'ScopedMockTimeMessageLoopTaskRunner is deprecated.',
+      ),
+      True,
+      (),
     )
 )
 
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 457d8716..5f2a418 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -2170,6 +2170,7 @@
     "test/scoped_feature_list_unittest.cc",
     "test/scoped_mock_time_message_loop_task_runner_unittest.cc",
     "test/scoped_task_environment_unittest.cc",
+    "test/test_mock_time_task_runner_unittest.cc",
     "test/test_pending_task_unittest.cc",
     "test/test_reg_util_win_unittest.cc",
     "test/trace_event_analyzer_unittest.cc",
diff --git a/base/run_loop.cc b/base/run_loop.cc
index b11e71364..96af51e 100644
--- a/base/run_loop.cc
+++ b/base/run_loop.cc
@@ -244,10 +244,40 @@
   tls_delegate.Get().Get()->active_run_loops_.top()->QuitWhenIdle();
 }
 
+#if DCHECK_IS_ON()
+RunLoop::ScopedDisallowRunningForTesting::ScopedDisallowRunningForTesting()
+    : current_delegate_(tls_delegate.Get().Get()),
+      previous_run_allowance_(
+          current_delegate_ ? current_delegate_->allow_running_for_testing_
+                            : false) {
+  if (current_delegate_)
+    current_delegate_->allow_running_for_testing_ = false;
+}
+
+RunLoop::ScopedDisallowRunningForTesting::~ScopedDisallowRunningForTesting() {
+  DCHECK_EQ(current_delegate_, tls_delegate.Get().Get());
+  if (current_delegate_)
+    current_delegate_->allow_running_for_testing_ = previous_run_allowance_;
+}
+#else   // DCHECK_IS_ON()
+// Defined out of line so that the compiler doesn't inline these and realize
+// the scope has no effect and then throws an "unused variable" warning in
+// non-dcheck builds.
+RunLoop::ScopedDisallowRunningForTesting::ScopedDisallowRunningForTesting() =
+    default;
+RunLoop::ScopedDisallowRunningForTesting::~ScopedDisallowRunningForTesting() =
+    default;
+#endif  // DCHECK_IS_ON()
+
 bool RunLoop::BeforeRun() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
 #if DCHECK_IS_ON()
+  DCHECK(delegate_->allow_running_for_testing_)
+      << "RunLoop::Run() isn't allowed in the scope of a "
+         "ScopedDisallowRunningForTesting. Hint: if mixing "
+         "TestMockTimeTaskRunners on same thread, use TestMockTimeTaskRunner's "
+         "API instead of RunLoop to drive individual task runners.";
   DCHECK(!run_called_);
   run_called_ = true;
 #endif  // DCHECK_IS_ON()
diff --git a/base/run_loop.h b/base/run_loop.h
index 8c6e649..dcabfc7 100644
--- a/base/run_loop.h
+++ b/base/run_loop.h
@@ -226,6 +226,10 @@
     RunLoopStack active_run_loops_;
     ObserverList<RunLoop::NestingObserver> nesting_observers_;
 
+#if DCHECK_IS_ON()
+    bool allow_running_for_testing_ = true;
+#endif
+
     // True once this Delegate is bound to a thread via
     // RegisterDelegateForCurrentThread().
     bool bound_ = false;
@@ -253,6 +257,28 @@
   static void QuitCurrentDeprecated();
   static void QuitCurrentWhenIdleDeprecated();
 
+  // Run() will DCHECK if called while there's a ScopedDisallowRunningForTesting
+  // in scope on its thread. This is useful to add safety to some test
+  // constructs which allow multiple task runners to share the main thread in
+  // unit tests. While the main thread can be shared by multiple runners to
+  // deterministically fake multi threading, there can still only be a single
+  // RunLoop::Delegate per thread and RunLoop::Run() should only be invoked from
+  // it (or it would result in incorrectly driving TaskRunner A while in
+  // TaskRunner B's context).
+  class BASE_EXPORT ScopedDisallowRunningForTesting {
+   public:
+    ScopedDisallowRunningForTesting();
+    ~ScopedDisallowRunningForTesting();
+
+   private:
+#if DCHECK_IS_ON()
+    Delegate* current_delegate_;
+    const bool previous_run_allowance_;
+#endif  // DCHECK_IS_ON()
+
+    DISALLOW_COPY_AND_ASSIGN(ScopedDisallowRunningForTesting);
+  };
+
  private:
 #if defined(OS_ANDROID)
   // Android doesn't support the blocking RunLoop::Run, so it calls
diff --git a/base/run_loop_unittest.cc b/base/run_loop_unittest.cc
index ac4f896..b739242 100644
--- a/base/run_loop_unittest.cc
+++ b/base/run_loop_unittest.cc
@@ -524,6 +524,17 @@
 }
 #endif  // defined(GTEST_HAS_DEATH_TEST) && !defined(OS_ANDROID)
 
+TEST_P(RunLoopTest, DisallowRunningForTesting) {
+  RunLoop::ScopedDisallowRunningForTesting disallow_running;
+  EXPECT_DCHECK_DEATH({ run_loop_.Run(); });
+}
+
+TEST_P(RunLoopTest, ExpiredDisallowRunningForTesting) {
+  { RunLoop::ScopedDisallowRunningForTesting disallow_running; }
+  // Running should be fine after |disallow_running| goes out of scope.
+  run_loop_.RunUntilIdle();
+}
+
 INSTANTIATE_TEST_CASE_P(Real,
                         RunLoopTest,
                         testing::Values(RunLoopTestType::kRealEnvironment));
diff --git a/base/test/scoped_mock_time_message_loop_task_runner.h b/base/test/scoped_mock_time_message_loop_task_runner.h
index fe0d2c4..dc8d9eeb 100644
--- a/base/test/scoped_mock_time_message_loop_task_runner.h
+++ b/base/test/scoped_mock_time_message_loop_task_runner.h
@@ -21,6 +21,10 @@
 // Note: RunLoop() will not work in the scope of a
 // ScopedMockTimeMessageLoopTaskRunner, the underlying TestMockTimeTaskRunner's
 // methods must be used instead to pump tasks.
+//
+// DEPRECATED: Use a TestMockTimeTaskRunner::Type::kBoundToThread instead of a
+// MessageLoop + ScopedMockTimeMessageLoopTaskRunner.
+// TODO(gab): Remove usage of this API and delete it.
 class ScopedMockTimeMessageLoopTaskRunner {
  public:
   ScopedMockTimeMessageLoopTaskRunner();
diff --git a/base/test/test_mock_time_task_runner.cc b/base/test/test_mock_time_task_runner.cc
index 9268d7b..4f482de6 100644
--- a/base/test/test_mock_time_task_runner.cc
+++ b/base/test/test_mock_time_task_runner.cc
@@ -70,6 +70,49 @@
   return task_runner_->Now();
 }
 
+// A SingleThreadTaskRunner which forwards everything to its |target_|. This is
+// useful to break ownership chains when it is known that |target_| will outlive
+// the NonOwningProxyTaskRunner it's injected into. In particular,
+// TestMockTimeTaskRunner is forced to be ref-counted by virtue of being a
+// SingleThreadTaskRunner. As such it is impossible for it to have a
+// ThreadTaskRunnerHandle member that points back to itself as the
+// ThreadTaskRunnerHandle which it owns would hold a ref back to it. To break
+// this dependency cycle, the ThreadTaskRunnerHandle is instead handed a
+// NonOwningProxyTaskRunner which allows the TestMockTimeTaskRunner to not hand
+// a ref to its ThreadTaskRunnerHandle while promising in return that it will
+// outlive that ThreadTaskRunnerHandle instance.
+class NonOwningProxyTaskRunner : public SingleThreadTaskRunner {
+ public:
+  explicit NonOwningProxyTaskRunner(SingleThreadTaskRunner* target)
+      : target_(target) {
+    DCHECK(target_);
+  }
+
+  // SingleThreadTaskRunner:
+  bool RunsTasksInCurrentSequence() const override {
+    return target_->RunsTasksInCurrentSequence();
+  }
+  bool PostDelayedTask(const tracked_objects::Location& from_here,
+                       OnceClosure task,
+                       TimeDelta delay) override {
+    return target_->PostDelayedTask(from_here, std::move(task), delay);
+  }
+  bool PostNonNestableDelayedTask(const tracked_objects::Location& from_here,
+                                  OnceClosure task,
+                                  TimeDelta delay) override {
+    return target_->PostNonNestableDelayedTask(from_here, std::move(task),
+                                               delay);
+  }
+
+ private:
+  friend class RefCountedThreadSafe<NonOwningProxyTaskRunner>;
+  ~NonOwningProxyTaskRunner() override = default;
+
+  SingleThreadTaskRunner* const target_;
+
+  DISALLOW_COPY_AND_ASSIGN(NonOwningProxyTaskRunner);
+};
+
 }  // namespace
 
 // TestMockTimeTaskRunner::TestOrderedPendingTask -----------------------------
@@ -145,13 +188,19 @@
   return first_task.GetTimeToRun() > second_task.GetTimeToRun();
 }
 
-TestMockTimeTaskRunner::TestMockTimeTaskRunner()
-    : now_(Time::UnixEpoch()), next_task_ordinal_(0) {
-}
+TestMockTimeTaskRunner::TestMockTimeTaskRunner(Type type)
+    : TestMockTimeTaskRunner(Time::UnixEpoch(), TimeTicks(), type) {}
 
 TestMockTimeTaskRunner::TestMockTimeTaskRunner(Time start_time,
-                                               TimeTicks start_ticks)
-    : now_(start_time), now_ticks_(start_ticks), next_task_ordinal_(0) {}
+                                               TimeTicks start_ticks,
+                                               Type type)
+    : now_(start_time), now_ticks_(start_ticks), tasks_lock_cv_(&tasks_lock_) {
+  if (type == Type::kBoundToThread) {
+    run_loop_client_ = RunLoop::RegisterDelegateForCurrentThread(this);
+    thread_task_runner_handle_ = std::make_unique<ThreadTaskRunnerHandle>(
+        MakeRefCounted<NonOwningProxyTaskRunner>(this));
+  }
+}
 
 TestMockTimeTaskRunner::~TestMockTimeTaskRunner() {
 }
@@ -256,10 +305,6 @@
   return PostDelayedTask(from_here, std::move(task), delay);
 }
 
-bool TestMockTimeTaskRunner::IsElapsingStopped() {
-  return false;
-}
-
 void TestMockTimeTaskRunner::OnBeforeSelectingTask() {
   // Empty default implementation.
 }
@@ -285,7 +330,7 @@
   }
 
   const TimeTicks original_now_ticks = now_ticks_;
-  while (!IsElapsingStopped()) {
+  while (!quit_run_loop_) {
     OnBeforeSelectingTask();
     TestPendingTask task_info;
     if (!DequeueNextTask(original_now_ticks, max_delta, &task_info))
@@ -312,6 +357,7 @@
 bool TestMockTimeTaskRunner::DequeueNextTask(const TimeTicks& reference,
                                              const TimeDelta& max_delta,
                                              TestPendingTask* next_task) {
+  DCHECK(thread_checker_.CalledOnValidThread());
   AutoLock scoped_lock(tasks_lock_);
   if (!tasks_.empty() &&
       (tasks_.top().GetTimeToRun() - reference) <= max_delta) {
@@ -324,4 +370,50 @@
   return false;
 }
 
+void TestMockTimeTaskRunner::Run() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  // Since TestMockTimeTaskRunner doesn't process system messages: there's no
+  // hope for anything but a chrome task to call Quit(). If this RunLoop can't
+  // process chrome tasks (i.e. disallowed by default in nested RunLoops), it's
+  // thereby guaranteed to hang...
+  DCHECK(run_loop_client_->ProcessingTasksAllowed())
+      << "This is a nested RunLoop instance and needs to be of "
+         "Type::NESTABLE_TASKS_ALLOWED.";
+
+  while (!quit_run_loop_) {
+    RunUntilIdle();
+    if (quit_run_loop_ || run_loop_client_->ShouldQuitWhenIdle())
+      break;
+
+    // Peek into |tasks_| to perform one of two things:
+    //   A) If there are no remaining tasks, wait until one is posted and
+    //      restart from the top.
+    //   B) If there is a remaining delayed task. Fast-forward to reach the next
+    //      round of tasks.
+    TimeDelta auto_fast_forward_by;
+    {
+      AutoLock scoped_lock(tasks_lock_);
+      if (tasks_.empty()) {
+        while (tasks_.empty())
+          tasks_lock_cv_.Wait();
+        continue;
+      }
+      auto_fast_forward_by = tasks_.top().GetTimeToRun() - now_ticks_;
+    }
+    FastForwardBy(auto_fast_forward_by);
+  }
+  quit_run_loop_ = false;
+}
+
+void TestMockTimeTaskRunner::Quit() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  quit_run_loop_ = true;
+}
+
+void TestMockTimeTaskRunner::EnsureWorkScheduled() {
+  // Nothing to do: TestMockTimeTaskRunner::Run() will always process tasks and
+  // doesn't need an extra kick on nested runs.
+}
+
 }  // namespace base
diff --git a/base/test/test_mock_time_task_runner.h b/base/test/test_mock_time_task_runner.h
index 73b0e53..6218734 100644
--- a/base/test/test_mock_time_task_runner.h
+++ b/base/test/test_mock_time_task_runner.h
@@ -15,6 +15,7 @@
 #include "base/callback.h"
 #include "base/callback_helpers.h"
 #include "base/macros.h"
+#include "base/run_loop.h"
 #include "base/single_thread_task_runner.h"
 #include "base/synchronization/lock.h"
 #include "base/test/test_pending_task.h"
@@ -25,6 +26,7 @@
 
 class Clock;
 class TickClock;
+class ThreadTaskRunnerHandle;
 
 // Runs pending tasks in the order of the tasks' post time + delay, and keeps
 // track of a mock (virtual) tick clock time that can be fast-forwarded.
@@ -33,7 +35,7 @@
 //
 //   - Methods RunsTasksInCurrentSequence() and Post[Delayed]Task() can be
 //     called from any thread, but the rest of the methods must be called on
-//     the same thread the TaskRunner was created on.
+//     the same thread the TestMockTimeTaskRunner was created on.
 //   - It allows for reentrancy, in that it handles the running of tasks that in
 //     turn call back into it (e.g., to post more tasks).
 //   - Tasks are stored in a priority queue, and executed in the increasing
@@ -43,17 +45,34 @@
 //     posted task delays and fast-forward increments is still representable by
 //     a TimeDelta, and that adding this delta to the starting values of Time
 //     and TickTime is still within their respective range.
-//   - Tasks aren't guaranteed to be destroyed immediately after they're run.
+//
+// A TestMockTimeTaskRunner of Type::kBoundToThread has the following additional
+// properties:
+//   - Thread/SequencedTaskRunnerHandle refers to it on its thread.
+//   - It can be driven by a RunLoop on the thread it was created on.
+//     RunLoop::Run() will result in running non-delayed tasks until idle and
+//     then, if RunLoop::QuitWhenIdle() wasn't invoked, fast-forwarding time to
+//     the next delayed task and looping again. And so on, until either
+//     RunLoop::Quit() is invoked (quits immediately after the current task) or
+//     RunLoop::QuitWhenIdle() is invoked (quits before having to fast forward
+//     time once again). Should RunLoop::Run() process all tasks (including
+//     delayed ones), it will block until more are posted. As usual,
+//     RunLoop::RunUntilIdle() is equivalent to RunLoop::Run() followed by an
+//     immediate RunLoop::QuitWhenIdle().
+//    -
 //
 // This is a slightly more sophisticated version of TestSimpleTaskRunner, in
 // that it supports running delayed tasks in the correct temporal order.
-class TestMockTimeTaskRunner : public SingleThreadTaskRunner {
+class TestMockTimeTaskRunner : public SingleThreadTaskRunner,
+                               public RunLoop::Delegate {
  public:
   // Everything that is executed in the scope of a ScopedContext will behave as
   // though it ran under |scope| (i.e. ThreadTaskRunnerHandle,
   // RunsTasksInCurrentSequence, etc.). This allows the test body to be all in
-  // one block when multiple TestMockTimeTaskRunners share the main thread. For
-  // example:
+  // one block when multiple TestMockTimeTaskRunners share the main thread.
+  // Note: RunLoop isn't supported: will DCHECK if used inside a ScopedContext.
+  //
+  // For example:
   //
   //   class ExampleFixture {
   //    protected:
@@ -86,7 +105,7 @@
    public:
     // Note: |scope| is ran until idle as part of this constructor to ensure
     // that anything which runs in the underlying scope runs after any already
-    // pending tasks (the contrary would break the SequencedTraskRunner
+    // pending tasks (the contrary would break the SequencedTaskRunner
     // contract).
     explicit ScopedContext(scoped_refptr<TestMockTimeTaskRunner> scope);
     ~ScopedContext();
@@ -96,12 +115,25 @@
     DISALLOW_COPY_AND_ASSIGN(ScopedContext);
   };
 
+  enum class Type {
+    // A TestMockTimeTaskRunner which can only be driven directly through its
+    // API. Thread/SequencedTaskRunnerHandle will refer to it only in the scope
+    // of its tasks.
+    kStandalone,
+    // A TestMockTimeTaskRunner which will associate to the thread it is created
+    // on, enabling RunLoop to drive it and making
+    // Thread/SequencedTaskRunnerHandle refer to it on that thread.
+    kBoundToThread,
+  };
+
   // Constructs an instance whose virtual time will start at the Unix epoch, and
   // whose time ticks will start at zero.
-  TestMockTimeTaskRunner();
+  TestMockTimeTaskRunner(Type type = Type::kStandalone);
 
   // Constructs an instance starting at the given virtual time and time ticks.
-  TestMockTimeTaskRunner(Time start_time, TimeTicks start_ticks);
+  TestMockTimeTaskRunner(Time start_time,
+                         TimeTicks start_ticks,
+                         Type type = Type::kStandalone);
 
   // Fast-forwards virtual time by |delta|, causing all tasks with a remaining
   // delay less than or equal to |delta| to be executed. |delta| must be
@@ -150,11 +182,6 @@
  protected:
   ~TestMockTimeTaskRunner() override;
 
-  // Whether the elapsing of virtual time is stopped or not. Subclasses can
-  // override this method to perform early exits from a running task runner.
-  // Defaults to always return false.
-  virtual bool IsElapsingStopped();
-
   // Called before the next task to run is selected, so that subclasses have a
   // last chance to make sure all tasks are posted.
   virtual void OnBeforeSelectingTask();
@@ -199,6 +226,11 @@
                        const TimeDelta& max_delta,
                        TestPendingTask* next_task);
 
+  // RunLoop::Delegate:
+  void Run() override;
+  void Quit() override;
+  void EnsureWorkScheduled() override;
+
   // Also used for non-dcheck logic (RunsTasksInCurrentSequence()) and as such
   // needs to be a ThreadCheckerImpl.
   ThreadCheckerImpl thread_checker_;
@@ -212,9 +244,19 @@
 
   // The ordinal to use for the next task. Must only be accessed while the
   // |tasks_lock_| is held.
-  size_t next_task_ordinal_;
+  size_t next_task_ordinal_ = 0;
 
   Lock tasks_lock_;
+  ConditionVariable tasks_lock_cv_;
+
+  // Members used to in TestMockTimeTaskRunners of Type::kBoundToThread to take
+  // ownership of the thread it was created on.
+  RunLoop::Delegate::Client* run_loop_client_ = nullptr;
+  std::unique_ptr<ThreadTaskRunnerHandle> thread_task_runner_handle_;
+
+  // Set to true in RunLoop::Delegate::Quit() to signal the topmost
+  // RunLoop::Delegate::Run() instance to stop, reset to false when it does.
+  bool quit_run_loop_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(TestMockTimeTaskRunner);
 };
diff --git a/base/test/test_mock_time_task_runner_unittest.cc b/base/test/test_mock_time_task_runner_unittest.cc
new file mode 100644
index 0000000..5ecf9d4e
--- /dev/null
+++ b/base/test/test_mock_time_task_runner_unittest.cc
@@ -0,0 +1,181 @@
+// Copyright 2017 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 "base/test/test_mock_time_task_runner.h"
+
+#include "base/memory/ref_counted.h"
+#include "base/run_loop.h"
+#include "base/test/gtest_util.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+// Basic usage should work the same from default and bound
+// TestMockTimeTaskRunners.
+TEST(TestMockTimeTaskRunnerTest, Basic) {
+  static constexpr TestMockTimeTaskRunner::Type kTestCases[] = {
+      TestMockTimeTaskRunner::Type::kStandalone,
+      TestMockTimeTaskRunner::Type::kBoundToThread};
+
+  for (auto type : kTestCases) {
+    SCOPED_TRACE(static_cast<int>(type));
+
+    auto mock_time_task_runner = MakeRefCounted<TestMockTimeTaskRunner>(type);
+    int counter = 0;
+
+    mock_time_task_runner->PostTask(
+        FROM_HERE,
+        base::Bind([](int* counter) { *counter += 1; }, Unretained(&counter)));
+    mock_time_task_runner->PostTask(
+        FROM_HERE,
+        base::Bind([](int* counter) { *counter += 32; }, Unretained(&counter)));
+    mock_time_task_runner->PostDelayedTask(
+        FROM_HERE,
+        base::Bind([](int* counter) { *counter += 256; }, Unretained(&counter)),
+        TimeDelta::FromSeconds(3));
+    mock_time_task_runner->PostDelayedTask(
+        FROM_HERE,
+        base::Bind([](int* counter) { *counter += 64; }, Unretained(&counter)),
+        TimeDelta::FromSeconds(1));
+    mock_time_task_runner->PostDelayedTask(
+        FROM_HERE,
+        base::Bind([](int* counter) { *counter += 1024; },
+                   Unretained(&counter)),
+        TimeDelta::FromMinutes(20));
+    mock_time_task_runner->PostDelayedTask(
+        FROM_HERE,
+        base::Bind([](int* counter) { *counter += 4096; },
+                   Unretained(&counter)),
+        TimeDelta::FromDays(20));
+
+    int expected_value = 0;
+    EXPECT_EQ(expected_value, counter);
+    mock_time_task_runner->RunUntilIdle();
+    expected_value += 1;
+    expected_value += 32;
+    EXPECT_EQ(expected_value, counter);
+
+    mock_time_task_runner->RunUntilIdle();
+    EXPECT_EQ(expected_value, counter);
+
+    mock_time_task_runner->FastForwardBy(TimeDelta::FromSeconds(1));
+    expected_value += 64;
+    EXPECT_EQ(expected_value, counter);
+
+    mock_time_task_runner->FastForwardBy(TimeDelta::FromSeconds(5));
+    expected_value += 256;
+    EXPECT_EQ(expected_value, counter);
+
+    mock_time_task_runner->FastForwardUntilNoTasksRemain();
+    expected_value += 1024;
+    expected_value += 4096;
+    EXPECT_EQ(expected_value, counter);
+  }
+}
+
+// A default TestMockTimeTaskRunner shouldn't result in a thread association.
+TEST(TestMockTimeTaskRunnerTest, DefaultUnbound) {
+  auto unbound_mock_time_task_runner = MakeRefCounted<TestMockTimeTaskRunner>();
+  EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
+  EXPECT_FALSE(SequencedTaskRunnerHandle::IsSet());
+  EXPECT_DCHECK_DEATH({ RunLoop().RunUntilIdle(); });
+}
+
+TEST(TestMockTimeTaskRunnerTest, RunLoopDriveableWhenBound) {
+  auto bound_mock_time_task_runner = MakeRefCounted<TestMockTimeTaskRunner>(
+      TestMockTimeTaskRunner::Type::kBoundToThread);
+
+  int counter = 0;
+  ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::Bind([](int* counter) { *counter += 1; }, Unretained(&counter)));
+  ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::Bind([](int* counter) { *counter += 32; }, Unretained(&counter)));
+  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind([](int* counter) { *counter += 256; }, Unretained(&counter)),
+      TimeDelta::FromSeconds(3));
+  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind([](int* counter) { *counter += 64; }, Unretained(&counter)),
+      TimeDelta::FromSeconds(1));
+  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind([](int* counter) { *counter += 1024; }, Unretained(&counter)),
+      TimeDelta::FromMinutes(20));
+  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind([](int* counter) { *counter += 4096; }, Unretained(&counter)),
+      TimeDelta::FromDays(20));
+
+  int expected_value = 0;
+  EXPECT_EQ(expected_value, counter);
+  RunLoop().RunUntilIdle();
+  expected_value += 1;
+  expected_value += 32;
+  EXPECT_EQ(expected_value, counter);
+
+  RunLoop().RunUntilIdle();
+  EXPECT_EQ(expected_value, counter);
+
+  {
+    RunLoop run_loop;
+    ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE, run_loop.QuitClosure(), TimeDelta::FromSeconds(1));
+    ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE,
+        base::Bind([](int* counter) { *counter += 8192; },
+                   Unretained(&counter)),
+        TimeDelta::FromSeconds(1));
+
+    // The QuitClosure() should be ordered between the 64 and the 8192
+    // increments and should preempt the latter.
+    run_loop.Run();
+    expected_value += 64;
+    EXPECT_EQ(expected_value, counter);
+
+    // Running until idle should process the 8192 increment whose delay has
+    // expired in the previous Run().
+    RunLoop().RunUntilIdle();
+    expected_value += 8192;
+    EXPECT_EQ(expected_value, counter);
+  }
+
+  {
+    RunLoop run_loop;
+    ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE, run_loop.QuitWhenIdleClosure(), TimeDelta::FromSeconds(5));
+    ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE,
+        base::Bind([](int* counter) { *counter += 16384; },
+                   Unretained(&counter)),
+        TimeDelta::FromSeconds(5));
+
+    // The QuitWhenIdleClosure() shouldn't preempt equally delayed tasks and as
+    // such the 16384 increment should be processed before quitting.
+    run_loop.Run();
+    expected_value += 256;
+    expected_value += 16384;
+    EXPECT_EQ(expected_value, counter);
+  }
+
+  // Process the remaining tasks (note: do not mimic this elsewhere,
+  // TestMockTimeTaskRunner::FastForwardUntilNoTasksRemain() is a better API to
+  // do this, this is just done here for the purpose of extensively testing the
+  // RunLoop approach).
+  RunLoop run_loop;
+  ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE, run_loop.QuitWhenIdleClosure(), TimeDelta::FromDays(50));
+
+  run_loop.Run();
+  expected_value += 1024;
+  expected_value += 4096;
+  EXPECT_EQ(expected_value, counter);
+}
+
+}  // namespace base
diff --git a/base/threading/thread_task_runner_handle.cc b/base/threading/thread_task_runner_handle.cc
index 11c9b6c..883f921d 100644
--- a/base/threading/thread_task_runner_handle.cc
+++ b/base/threading/thread_task_runner_handle.cc
@@ -10,6 +10,7 @@
 #include "base/lazy_instance.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
+#include "base/run_loop.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "base/threading/thread_local.h"
 
@@ -53,9 +54,8 @@
   DCHECK(!SequencedTaskRunnerHandle::IsSet() || IsSet());
 
   if (!IsSet()) {
-    std::unique_ptr<ThreadTaskRunnerHandle> top_level_ttrh =
-        std::make_unique<ThreadTaskRunnerHandle>(
-            std::move(overriding_task_runner));
+    auto top_level_ttrh = std::make_unique<ThreadTaskRunnerHandle>(
+        std::move(overriding_task_runner));
     return ScopedClosureRunner(base::Bind(
         [](std::unique_ptr<ThreadTaskRunnerHandle> ttrh_to_release) {},
         base::Passed(&top_level_ttrh)));
@@ -66,9 +66,14 @@
   // previous one, as the |task_runner_to_restore|).
   ttrh->task_runner_.swap(overriding_task_runner);
 
+  auto no_running_during_override =
+      std::make_unique<RunLoop::ScopedDisallowRunningForTesting>();
+
   return ScopedClosureRunner(base::Bind(
       [](scoped_refptr<SingleThreadTaskRunner> task_runner_to_restore,
-         SingleThreadTaskRunner* expected_task_runner_before_restore) {
+         SingleThreadTaskRunner* expected_task_runner_before_restore,
+         std::unique_ptr<RunLoop::ScopedDisallowRunningForTesting>
+             no_running_during_override) {
         ThreadTaskRunnerHandle* ttrh = lazy_tls_ptr.Pointer()->Get();
 
         DCHECK_EQ(expected_task_runner_before_restore, ttrh->task_runner_.get())
@@ -78,7 +83,8 @@
         ttrh->task_runner_.swap(task_runner_to_restore);
       },
       base::Passed(&overriding_task_runner),
-      base::Unretained(ttrh->task_runner_.get())));
+      base::Unretained(ttrh->task_runner_.get()),
+      base::Passed(&no_running_during_override)));
 }
 
 ThreadTaskRunnerHandle::ThreadTaskRunnerHandle(
diff --git a/components/policy/core/common/remote_commands/remote_commands_service_unittest.cc b/components/policy/core/common/remote_commands/remote_commands_service_unittest.cc
index 1a8cc666..26fa373 100644
--- a/components/policy/core/common/remote_commands/remote_commands_service_unittest.cc
+++ b/components/policy/core/common/remote_commands/remote_commands_service_unittest.cc
@@ -172,84 +172,14 @@
   DISALLOW_COPY_AND_ASSIGN(TestingCloudPolicyClientForRemoteCommands);
 };
 
-// A scoped TestMockTimeTaskRunner capable to run tasks until Quit() is called.
-class ScopedMockTimeTaskRunner : public base::TestMockTimeTaskRunner {
- public:
-  class ScopedRunner {
-   public:
-    explicit ScopedRunner(
-        const scoped_refptr<ScopedMockTimeTaskRunner>& task_runner)
-        : task_runner_(task_runner) {
-      DCHECK(!task_runner_->attached_runner_);
-      task_runner_->attached_runner_ = this;
-    }
-
-    virtual ~ScopedRunner() {
-      DCHECK_EQ(this, task_runner_->attached_runner_);
-      DCHECK(run_called_);
-      DCHECK(quit_called_);
-
-      task_runner_->attached_runner_ = nullptr;
-    }
-
-    void Run() {
-      DCHECK(!run_called_);
-      run_called_ = true;
-
-      // It's okay to call Quit() before calling Run().
-      if (quit_called_)
-        return;
-      task_runner_->FastForwardUntilNoTasksRemain();
-    }
-
-    void Quit() {
-      DCHECK(!quit_called_);
-      quit_called_ = true;
-    }
-
-    base::Closure QuitClosure() {
-      // It's safe to use Unretained here since Quit() is required to be
-      // called before dtor is called.
-      return base::Bind(&ScopedRunner::Quit, base::Unretained(this));
-    }
-
-   private:
-    friend class ScopedMockTimeTaskRunner;
-
-    bool run_called_ = false;
-    bool quit_called_ = false;
-
-    scoped_refptr<ScopedMockTimeTaskRunner> task_runner_;
-
-    DISALLOW_COPY_AND_ASSIGN(ScopedRunner);
-  };
-
-  ScopedMockTimeTaskRunner() {}
-
- private:
-  ~ScopedMockTimeTaskRunner() override { DCHECK(!attached_runner_); }
-
-  bool IsElapsingStopped() override {
-    return attached_runner_ && attached_runner_->quit_called_;
-  }
-
-  // Points to the current attached ScopedRunner, and is null if no runner is
-  // attached.
-  ScopedRunner* attached_runner_ = nullptr;
-
-  DISALLOW_COPY_AND_ASSIGN(ScopedMockTimeTaskRunner);
-};
-
 // Base class for unit tests regarding remote commands service.
 class RemoteCommandsServiceTest : public testing::Test {
  protected:
-  RemoteCommandsServiceTest()
-      : task_runner_(new ScopedMockTimeTaskRunner()),
-        runner_handle_(task_runner_) {}
+  RemoteCommandsServiceTest() = default;
 
   void SetUp() override {
     server_.reset(new TestingRemoteCommandsServer());
-    server_->SetClock(task_runner_->GetMockTickClock());
+    server_->SetClock(mock_task_runner_->GetMockTickClock());
     cloud_policy_client_.reset(
         new TestingCloudPolicyClientForRemoteCommands(server_.get()));
   }
@@ -264,22 +194,20 @@
     remote_commands_service_.reset(new RemoteCommandsService(
         std::move(factory), cloud_policy_client_.get()));
     remote_commands_service_->SetClockForTesting(
-        task_runner_->GetMockTickClock());
+        mock_task_runner_->GetMockTickClock());
   }
 
-  void FlushAllTasks() {
-    task_runner_->FastForwardUntilNoTasksRemain();
-  }
+  void FlushAllTasks() { mock_task_runner_->FastForwardUntilNoTasksRemain(); }
 
   std::unique_ptr<TestingRemoteCommandsServer> server_;
   std::unique_ptr<TestingCloudPolicyClientForRemoteCommands>
       cloud_policy_client_;
   std::unique_ptr<RemoteCommandsService> remote_commands_service_;
 
-  scoped_refptr<ScopedMockTimeTaskRunner> task_runner_;
-
  private:
-  base::ThreadTaskRunnerHandle runner_handle_;
+  const scoped_refptr<base::TestMockTimeTaskRunner> mock_task_runner_ =
+      base::MakeRefCounted<base::TestMockTimeTaskRunner>(
+          base::TestMockTimeTaskRunner::Type::kBoundToThread);
 
   DISALLOW_COPY_AND_ASSIGN(RemoteCommandsServiceTest);
 };
@@ -306,7 +234,7 @@
   EXPECT_CALL(*factory, BuildTestCommand()).Times(1);
 
   {
-    ScopedMockTimeTaskRunner::ScopedRunner scoped_runner(task_runner_);
+    base::RunLoop run_loop;
 
     // Issue a command before service started.
     server_->IssueCommand(em::RemoteCommand_Type_COMMAND_ECHO_TEST,
@@ -314,12 +242,11 @@
                           base::Bind(&ExpectSucceededJob, kTestPayload), false);
 
     // Start the service, run until the command is fetched.
-    cloud_policy_client_->ExpectFetchCommands(0u, 1u,
-                                              scoped_runner.QuitClosure());
+    cloud_policy_client_->ExpectFetchCommands(0u, 1u, run_loop.QuitClosure());
     StartService(std::move(factory));
     EXPECT_TRUE(remote_commands_service_->FetchRemoteCommands());
 
-    scoped_runner.Run();
+    run_loop.Run();
   }
 
   // And run again so that the result can be reported.
@@ -364,15 +291,14 @@
   StartService(std::move(factory));
 
   {
-    ScopedMockTimeTaskRunner::ScopedRunner scoped_runner(task_runner_);
+    base::RunLoop run_loop;
 
     // Add a command which will be issued after first fetch.
     server_->IssueCommand(em::RemoteCommand_Type_COMMAND_ECHO_TEST,
                           kTestPayload,
                           base::Bind(&ExpectSucceededJob, kTestPayload), true);
 
-    cloud_policy_client_->ExpectFetchCommands(0u, 0u,
-                                              scoped_runner.QuitClosure());
+    cloud_policy_client_->ExpectFetchCommands(0u, 0u, run_loop.QuitClosure());
 
     // Attempts to fetch commands.
     EXPECT_TRUE(remote_commands_service_->FetchRemoteCommands());
@@ -387,7 +313,7 @@
     EXPECT_FALSE(remote_commands_service_->FetchRemoteCommands());
 
     // Run until first fetch request is completed.
-    scoped_runner.Run();
+    run_loop.Run();
   }
 
   // The command should be issued now. Note that this command was actually
diff --git a/remoting/host/heartbeat_sender_unittest.cc b/remoting/host/heartbeat_sender_unittest.cc
index 37da688..62dfcee5 100644
--- a/remoting/host/heartbeat_sender_unittest.cc
+++ b/remoting/host/heartbeat_sender_unittest.cc
@@ -85,9 +85,9 @@
                     base::TimeDelta interval = base::TimeDelta(),
                     int expected_sequence_id = 0);
 
-  base::MessageLoop message_loop_;
   scoped_refptr<base::TestMockTimeTaskRunner> fake_time_task_runner_ =
-      new base::TestMockTimeTaskRunner();
+      base::MakeRefCounted<base::TestMockTimeTaskRunner>(
+          base::TestMockTimeTaskRunner::Type::kBoundToThread);
 
   FakeSignalStrategy signal_strategy_;
   FakeSignalStrategy bot_signal_strategy_;
@@ -283,19 +283,14 @@
 }
 
 TEST_F(HeartbeatSenderTest, ResponseTimeout) {
-  base::TestMockTimeTaskRunner::ScopedContext scoped_context(
-      fake_time_task_runner_.get());
-
   signal_strategy_.Connect();
   base::RunLoop().RunUntilIdle();
 
   // Simulate heartbeat timeout.
   fake_time_task_runner_->FastForwardBy(base::TimeDelta::FromMinutes(1));
-  base::RunLoop().RunUntilIdle();
 
   // SignalStrategy should be disconnected in response to the second timeout.
   fake_time_task_runner_->FastForwardBy(base::TimeDelta::FromMinutes(1));
-  base::RunLoop().RunUntilIdle();
 
   EXPECT_EQ(SignalStrategy::DISCONNECTED, signal_strategy_.GetState());
 }