content: Add a TaskTraits extension and TaskExecutor for BrowserThreads.

This allows using //base/task/post_task.h for posting tasks to a
BrowserThread by specifying a BrowserThread::ID as a task trait.

Also adds a content::NonNestable task trait to support non-nestable
tasks in base::PostTaskWithTraits.

In the future, we will add further traits to facilitate scheduling
tasks onto different SequenceManager queues on the UI thread, see:
https://docs.google.com/document/d/1z1BDq9vzcEpkhN9LSPF5XMnZ0kLJ8mWWkNAi4OI7cos/edit?usp=sharing

Bug: 867421, 863341, 878356
Change-Id: Id8b7bc2e374917ceb421c7f6139790e6f1457511
Reviewed-on: https://chromium-review.googlesource.com/1181364
Commit-Queue: Eric Seckler <eseckler@chromium.org>
Reviewed-by: Sami Kyöstilä <skyostil@chromium.org>
Reviewed-by: Avi Drissman <avi@chromium.org>
Reviewed-by: François Doray <fdoray@chromium.org>
Reviewed-by: Alex Clarke <alexclarke@chromium.org>
Cr-Commit-Position: refs/heads/master@{#586728}
diff --git a/base/task/post_task_unittest.cc b/base/task/post_task_unittest.cc
index 467fdcb0..37876de 100644
--- a/base/task/post_task_unittest.cc
+++ b/base/task/post_task_unittest.cc
@@ -7,6 +7,7 @@
 #include "base/bind_helpers.h"
 #include "base/task/task_executor.h"
 #include "base/task/test_task_traits_extension.h"
+#include "base/test/gtest_util.h"
 #include "base/test/scoped_task_environment.h"
 #include "base/test/test_simple_task_runner.h"
 #include "build/build_config.h"
@@ -186,4 +187,9 @@
   }
 }
 
+TEST_F(PostTaskTestWithExecutor, RegisterExecutorTwice) {
+  EXPECT_DCHECK_DEATH(
+      RegisterTaskExecutor(TestTaskTraitsExtension::kExtensionId, &executor_));
+}
+
 }  // namespace base
diff --git a/base/task/task_traits_details.h b/base/task/task_traits_details.h
index 11001e7..d916948 100644
--- a/base/task/task_traits_details.h
+++ b/base/task/task_traits_details.h
@@ -93,9 +93,12 @@
 //     Converts an argument of type ArgType into a value returned by
 //     GetValueFromArgListImpl().
 //
+// |getter| may provide:
+//
 // ValueType GetDefaultValue():
 //     Returns the value returned by GetValueFromArgListImpl() if none of its
-//     arguments is of type ArgType.
+//     arguments is of type ArgType. If this method is not provided, compilation
+//     will fail when no argument of type ArgType is provided.
 template <class GetterType, class... ArgTypes>
 constexpr typename GetterType::ValueType GetValueFromArgList(
     GetterType getter,
@@ -117,6 +120,12 @@
   constexpr ValueType GetDefaultValue() const { return DefaultValue; }
 };
 
+template <typename ArgType>
+struct RequiredEnumArgGetter {
+  using ValueType = ArgType;
+  constexpr ValueType GetValueFromArg(ArgType arg) const { return arg; }
+};
+
 // Tests whether a given trait type is valid or invalid by testing whether it is
 // convertible to the provided ValidTraits type. To use, define a ValidTraits
 // type like this:
diff --git a/content/app/content_main_runner_impl.cc b/content/app/content_main_runner_impl.cc
index c9fd1a4..4caa237 100644
--- a/content/app/content_main_runner_impl.cc
+++ b/content/app/content_main_runner_impl.cc
@@ -42,6 +42,7 @@
 #include "components/tracing/common/trace_startup.h"
 #include "content/app/mojo/mojo_init.h"
 #include "content/browser/browser_process_sub_thread.h"
+#include "content/browser/browser_thread_impl.h"
 #include "content/browser/startup_data_impl.h"
 #include "content/common/content_constants_internal.h"
 #include "content/common/url_schemes.h"
@@ -878,6 +879,10 @@
       base::TaskScheduler::Create("Browser");
     }
 
+    // Register the TaskExecutor for posting task to the BrowserThreads. It is
+    // incorrect to post to a BrowserThread before this point.
+    BrowserThreadImpl::CreateTaskExecutor();
+
     delegate_->PreCreateMainMessageLoop();
 #if defined(OS_WIN)
     if (l10n_util::GetLocaleOverrides().empty()) {
diff --git a/content/browser/browser_thread_browsertest.cc b/content/browser/browser_thread_browsertest.cc
new file mode 100644
index 0000000..25bf7fc
--- /dev/null
+++ b/content/browser/browser_thread_browsertest.cc
@@ -0,0 +1,51 @@
+// Copyright 2018 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/bind_helpers.h"
+#include "base/task/post_task.h"
+#include "base/test/gtest_util.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/test/content_browser_test.h"
+
+namespace content {
+
+class BrowserThreadPostTaskBeforeInitBrowserTest : public ContentBrowserTest {
+ protected:
+  void SetUp() override {
+    // This should fail because the TaskScheduler + TaskExecutor weren't created
+    // yet.
+    EXPECT_DCHECK_DEATH(base::PostTaskWithTraits(FROM_HERE, {BrowserThread::IO},
+                                                 base::DoNothing()));
+
+    // Obtaining a TaskRunner should also fail.
+    EXPECT_DCHECK_DEATH(base::CreateTaskRunnerWithTraits({BrowserThread::IO}));
+
+    ContentBrowserTest::SetUp();
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(BrowserThreadPostTaskBeforeInitBrowserTest,
+                       ExpectFailures) {}
+
+class BrowserThreadPostTaskBeforeThreadCreationBrowserTest
+    : public ContentBrowserTest {
+ protected:
+  void CreatedBrowserMainParts(BrowserMainParts* browser_main_parts) override {
+    // This should fail because the IO thread hasn't been initialized yet.
+    EXPECT_DCHECK_DEATH(base::PostTaskWithTraits(FROM_HERE, {BrowserThread::IO},
+                                                 base::DoNothing()));
+
+    // Obtaining a TaskRunner should work, because the TaskExecutor was
+    // registered already.
+    auto task_runner = base::CreateTaskRunnerWithTraits({BrowserThread::IO});
+    // But posting should still fail.
+    EXPECT_DCHECK_DEATH(task_runner->PostTask(FROM_HERE, base::DoNothing()));
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(BrowserThreadPostTaskBeforeThreadCreationBrowserTest,
+                       ExpectFailures) {}
+
+}  // namespace content
diff --git a/content/browser/browser_thread_impl.cc b/content/browser/browser_thread_impl.cc
index 4443c7b..15856a2 100644
--- a/content/browser/browser_thread_impl.cc
+++ b/content/browser/browser_thread_impl.cc
@@ -15,9 +15,11 @@
 #include "base/logging.h"
 #include "base/macros.h"
 #include "base/sequence_checker.h"
+#include "base/task/task_executor.h"
 #include "base/threading/platform_thread.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
+#include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/content_browser_client.h"
 
 namespace content {
@@ -161,6 +163,66 @@
   }
 }
 
+class BrowserThreadTaskExecutor : public base::TaskExecutor {
+ public:
+  BrowserThreadTaskExecutor() {}
+  ~BrowserThreadTaskExecutor() override {}
+
+  // base::TaskExecutor implementation.
+  bool PostDelayedTaskWithTraits(const base::Location& from_here,
+                                 const base::TaskTraits& traits,
+                                 base::OnceClosure task,
+                                 base::TimeDelta delay) override {
+    return PostTaskHelper(
+        GetBrowserThreadIdentifier(traits), from_here, std::move(task), delay,
+        traits.GetExtension<BrowserTaskTraitsExtension>().nestable());
+  }
+
+  scoped_refptr<base::TaskRunner> CreateTaskRunnerWithTraits(
+      const base::TaskTraits& traits) override {
+    return GetTaskRunnerForThread(GetBrowserThreadIdentifier(traits));
+  }
+
+  scoped_refptr<base::SequencedTaskRunner> CreateSequencedTaskRunnerWithTraits(
+      const base::TaskTraits& traits) override {
+    return GetTaskRunnerForThread(GetBrowserThreadIdentifier(traits));
+  }
+
+  scoped_refptr<base::SingleThreadTaskRunner>
+  CreateSingleThreadTaskRunnerWithTraits(
+      const base::TaskTraits& traits,
+      base::SingleThreadTaskRunnerThreadMode thread_mode) override {
+    DCHECK_EQ(thread_mode, base::SingleThreadTaskRunnerThreadMode::SHARED);
+    return GetTaskRunnerForThread(GetBrowserThreadIdentifier(traits));
+  }
+
+#if defined(OS_WIN)
+  scoped_refptr<base::SingleThreadTaskRunner> CreateCOMSTATaskRunnerWithTraits(
+      const base::TaskTraits& traits,
+      base::SingleThreadTaskRunnerThreadMode thread_mode) override {
+    DCHECK_EQ(GetBrowserThreadIdentifier(traits), BrowserThread::UI);
+    return CreateSingleThreadTaskRunnerWithTraits(traits, thread_mode);
+  }
+#endif  // defined(OS_WIN)
+
+ private:
+  BrowserThread::ID GetBrowserThreadIdentifier(const base::TaskTraits& traits) {
+    DCHECK_EQ(traits.extension_id(), BrowserTaskTraitsExtension::kExtensionId);
+    BrowserThread::ID id =
+        traits.GetExtension<BrowserTaskTraitsExtension>().browser_thread();
+    DCHECK_LT(id, BrowserThread::ID_COUNT);
+    return id;
+  }
+
+  scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunnerForThread(
+      BrowserThread::ID identifier) {
+    return g_task_runners.Get().proxies[identifier];
+  }
+};
+
+// |g_browser_thread_task_executor| is intentionally leaked on shutdown.
+BrowserThreadTaskExecutor* g_browser_thread_task_executor = nullptr;
+
 }  // namespace
 
 BrowserThreadImpl::BrowserThreadImpl(
@@ -339,4 +401,21 @@
   return g_task_runners.Get().proxies[identifier];
 }
 
+// static
+void BrowserThreadImpl::CreateTaskExecutor() {
+  DCHECK(!g_browser_thread_task_executor);
+  g_browser_thread_task_executor = new BrowserThreadTaskExecutor();
+  base::RegisterTaskExecutor(BrowserTaskTraitsExtension::kExtensionId,
+                             g_browser_thread_task_executor);
+}
+
+// static
+void BrowserThreadImpl::ResetTaskExecutorForTesting() {
+  DCHECK(g_browser_thread_task_executor);
+  base::UnregisterTaskExecutorForTesting(
+      BrowserTaskTraitsExtension::kExtensionId);
+  delete g_browser_thread_task_executor;
+  g_browser_thread_task_executor = nullptr;
+}
+
 }  // namespace content
diff --git a/content/browser/browser_thread_impl.h b/content/browser/browser_thread_impl.h
index cd0759c..5789a29 100644
--- a/content/browser/browser_thread_impl.h
+++ b/content/browser/browser_thread_impl.h
@@ -39,6 +39,13 @@
   // |identifier|.
   static void ResetGlobalsForTesting(BrowserThread::ID identifier);
 
+  // Creates and registers a TaskExecutor that facilitates posting tasks to a
+  // BrowserThread via //base/task/post_task.h.
+  static void CreateTaskExecutor();
+
+  // Unregister and delete the TaskExecutor after a test.
+  static void ResetTaskExecutorForTesting();
+
  private:
   // Restrict instantiation to BrowserProcessSubThread as it performs important
   // initialization that shouldn't be bypassed (except by BrowserMainLoop for
diff --git a/content/browser/browser_thread_unittest.cc b/content/browser/browser_thread_unittest.cc
index cf5b1635..f27b5b9 100644
--- a/content/browser/browser_thread_unittest.cc
+++ b/content/browser/browser_thread_unittest.cc
@@ -12,9 +12,13 @@
 #include "base/run_loop.h"
 #include "base/sequenced_task_runner_helpers.h"
 #include "base/single_thread_task_runner.h"
+#include "base/task/post_task.h"
+#include "base/test/scoped_task_environment.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
 #include "content/browser/browser_process_sub_thread.h"
 #include "content/browser/browser_thread_impl.h"
+#include "content/public/browser/browser_task_traits.h"
 #include "content/public/test/test_browser_thread.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "testing/platform_test.h"
@@ -33,6 +37,8 @@
 
  protected:
   void SetUp() override {
+    BrowserThreadImpl::CreateTaskExecutor();
+
     ui_thread_ = std::make_unique<BrowserProcessSubThread>(BrowserThread::UI);
     ui_thread_->Start();
 
@@ -51,6 +57,7 @@
 
     BrowserThreadImpl::ResetGlobalsForTesting(BrowserThread::UI);
     BrowserThreadImpl::ResetGlobalsForTesting(BrowserThread::IO);
+    BrowserThreadImpl::ResetTaskExecutorForTesting();
   }
 
   // Prepares this BrowserThreadTest for Release() to be invoked. |on_release|
@@ -59,8 +66,9 @@
     on_release_ = std::move(on_release);
   }
 
-  static void BasicFunction(base::OnceClosure continuation) {
-    EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
+  static void BasicFunction(base::OnceClosure continuation,
+                            BrowserThread::ID target) {
+    EXPECT_TRUE(BrowserThread::CurrentlyOn(target));
     std::move(continuation).Run();
   }
 
@@ -87,7 +95,7 @@
   std::unique_ptr<BrowserProcessSubThread> ui_thread_;
   std::unique_ptr<BrowserProcessSubThread> io_thread_;
 
-  base::MessageLoop loop_;
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
   // Must be set before Release() to verify the deletion is intentional. Will be
   // run from the next call to Release(). mutable so it can be consumed from
   // Release().
@@ -136,7 +144,17 @@
   base::RunLoop run_loop;
   BrowserThread::PostTask(
       BrowserThread::IO, FROM_HERE,
-      base::BindOnce(&BasicFunction, run_loop.QuitWhenIdleClosure()));
+      base::BindOnce(&BasicFunction, run_loop.QuitWhenIdleClosure(),
+                     BrowserThread::IO));
+  run_loop.Run();
+}
+
+TEST_F(BrowserThreadTest, PostTaskWithTraits) {
+  base::RunLoop run_loop;
+  EXPECT_TRUE(base::PostTaskWithTraits(
+      FROM_HERE, {BrowserThread::IO, NonNestable()},
+      base::BindOnce(&BasicFunction, run_loop.QuitWhenIdleClosure(),
+                     BrowserThread::IO)));
   run_loop.Run();
 }
 
@@ -161,11 +179,53 @@
       BrowserThread::GetTaskRunnerForThread(BrowserThread::IO);
   base::RunLoop run_loop;
   task_runner->PostTask(
-      FROM_HERE,
-      base::BindOnce(&BasicFunction, run_loop.QuitWhenIdleClosure()));
+      FROM_HERE, base::BindOnce(&BasicFunction, run_loop.QuitWhenIdleClosure(),
+                                BrowserThread::IO));
   run_loop.Run();
 }
 
+TEST_F(BrowserThreadTest, PostTaskViaTaskRunnerWithTraits) {
+  scoped_refptr<base::TaskRunner> task_runner =
+      base::CreateTaskRunnerWithTraits({BrowserThread::IO});
+  base::RunLoop run_loop;
+  EXPECT_TRUE(task_runner->PostTask(
+      FROM_HERE, base::BindOnce(&BasicFunction, run_loop.QuitWhenIdleClosure(),
+                                BrowserThread::IO)));
+  run_loop.Run();
+}
+
+TEST_F(BrowserThreadTest, PostTaskViaSequencedTaskRunnerWithTraits) {
+  scoped_refptr<base::SequencedTaskRunner> task_runner =
+      base::CreateSequencedTaskRunnerWithTraits({BrowserThread::IO});
+  base::RunLoop run_loop;
+  EXPECT_TRUE(task_runner->PostTask(
+      FROM_HERE, base::BindOnce(&BasicFunction, run_loop.QuitWhenIdleClosure(),
+                                BrowserThread::IO)));
+  run_loop.Run();
+}
+
+TEST_F(BrowserThreadTest, PostTaskViaSingleThreadTaskRunnerWithTraits) {
+  scoped_refptr<base::SingleThreadTaskRunner> task_runner =
+      base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::IO});
+  base::RunLoop run_loop;
+  EXPECT_TRUE(task_runner->PostTask(
+      FROM_HERE, base::BindOnce(&BasicFunction, run_loop.QuitWhenIdleClosure(),
+                                BrowserThread::IO)));
+  run_loop.Run();
+}
+
+#if defined(OS_WIN)
+TEST_F(BrowserThreadTest, PostTaskViaCOMSTATaskRunnerWithTraits) {
+  scoped_refptr<base::SingleThreadTaskRunner> task_runner =
+      base::CreateCOMSTATaskRunnerWithTraits({BrowserThread::UI});
+  base::RunLoop run_loop;
+  EXPECT_TRUE(task_runner->PostTask(
+      FROM_HERE, base::BindOnce(&BasicFunction, run_loop.QuitWhenIdleClosure(),
+                                BrowserThread::UI)));
+  run_loop.Run();
+}
+#endif  // defined(OS_WIN)
+
 TEST_F(BrowserThreadTest, ReleaseViaTaskRunner) {
   scoped_refptr<base::SingleThreadTaskRunner> task_runner =
       BrowserThread::GetTaskRunnerForThread(BrowserThread::UI);
@@ -176,6 +236,15 @@
   run_loop.Run();
 }
 
+TEST_F(BrowserThreadTest, ReleaseViaTaskRunnerWithTraits) {
+  scoped_refptr<base::SingleThreadTaskRunner> task_runner =
+      base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::UI});
+  base::RunLoop run_loop;
+  ExpectRelease(run_loop.QuitWhenIdleClosure());
+  task_runner->ReleaseSoon(FROM_HERE, this);
+  run_loop.Run();
+}
+
 TEST_F(BrowserThreadTest, PostTaskAndReply) {
   // Most of the heavy testing for PostTaskAndReply() is done inside the
   // task runner test.  This just makes sure we get piped through at all.
@@ -186,6 +255,16 @@
   run_loop.Run();
 }
 
+TEST_F(BrowserThreadTest, PostTaskAndReplyWithTraits) {
+  // Most of the heavy testing for PostTaskAndReply() is done inside the
+  // task runner test.  This just makes sure we get piped through at all.
+  base::RunLoop run_loop;
+  ASSERT_TRUE(base::PostTaskWithTraitsAndReply(FROM_HERE, {BrowserThread::IO},
+                                               base::DoNothing(),
+                                               run_loop.QuitWhenIdleClosure()));
+  run_loop.Run();
+}
+
 TEST_F(BrowserThreadTest, RunsTasksInCurrentSequencedDuringShutdown) {
   bool did_shutdown = false;
   base::RunLoop loop;
diff --git a/content/public/browser/BUILD.gn b/content/public/browser/BUILD.gn
index dac4d10..1ebae0a 100644
--- a/content/public/browser/BUILD.gn
+++ b/content/public/browser/BUILD.gn
@@ -79,6 +79,8 @@
     "browser_plugin_guest_manager.cc",
     "browser_plugin_guest_manager.h",
     "browser_ppapi_host.h",
+    "browser_task_traits.cc",
+    "browser_task_traits.h",
     "browser_thread.h",
     "browser_thread_delegate.h",
     "browser_url_handler.h",
diff --git a/content/public/browser/browser_task_traits.cc b/content/public/browser/browser_task_traits.cc
new file mode 100644
index 0000000..311902e
--- /dev/null
+++ b/content/public/browser/browser_task_traits.cc
@@ -0,0 +1,12 @@
+// Copyright 2018 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 "content/public/browser/browser_task_traits.h"
+
+namespace content {
+
+// static
+constexpr uint8_t BrowserTaskTraitsExtension::kExtensionId;
+
+}  // namespace content
diff --git a/content/public/browser/browser_task_traits.h b/content/public/browser/browser_task_traits.h
new file mode 100644
index 0000000..8ccee9d9
--- /dev/null
+++ b/content/public/browser/browser_task_traits.h
@@ -0,0 +1,102 @@
+// Copyright 2018 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.
+
+#ifndef CONTENT_PUBLIC_BROWSER_BROWSER_TASK_TRAITS_H_
+#define CONTENT_PUBLIC_BROWSER_BROWSER_TASK_TRAITS_H_
+
+#include "base/task/task_traits.h"
+#include "base/task/task_traits_extension.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace content {
+
+// Tasks with this trait will not be executed inside a nested RunLoop.
+//
+// Note: This should rarely be required. Drivers of nested loops should instead
+// make sure to be reentrant when allowing nested application tasks (also rare).
+//
+// TODO(https://crbug.com/876272): Investigate removing this trait -- and any
+// logic for deferred tasks in MessageLoop.
+struct NonNestable {};
+
+// TaskTraits for running tasks on the browser threads.
+//
+// These traits enable the use of the //base/task/post_task.h APIs to post tasks
+// to a BrowserThread.
+//
+// To post a task to the UI thread (analogous for IO thread):
+//     base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI}, task);
+//
+// To obtain a TaskRunner for the UI thread (analogous for the IO thread):
+//     base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::UI});
+//
+// See //base/task/post_task.h for more detailed documentation.
+//
+// Posting to a BrowserThread must only be done after it was initialized (ref.
+// BrowserMainLoop::CreateThreads() phase).
+class CONTENT_EXPORT BrowserTaskTraitsExtension {
+ public:
+  static constexpr uint8_t kExtensionId =
+      base::TaskTraitsExtensionStorage::kFirstEmbedderExtensionId;
+
+  struct ValidTrait {
+    ValidTrait(BrowserThread::ID) {}
+    ValidTrait(NonNestable) {}
+  };
+
+  template <
+      class... ArgTypes,
+      class CheckArgumentsAreValid = std::enable_if_t<
+          base::trait_helpers::AreValidTraits<ValidTrait, ArgTypes...>::value>>
+  constexpr BrowserTaskTraitsExtension(ArgTypes... args)
+      : browser_thread_(base::trait_helpers::GetValueFromArgList(
+            base::trait_helpers::RequiredEnumArgGetter<BrowserThread::ID>(),
+            args...)),
+        nestable_(!base::trait_helpers::GetValueFromArgList(
+            base::trait_helpers::BooleanArgGetter<NonNestable>(),
+            args...)) {}
+
+  constexpr base::TaskTraitsExtensionStorage Serialize() const {
+    static_assert(8 == sizeof(BrowserTaskTraitsExtension),
+                  "Update Serialize() and Parse() when changing "
+                  "BrowserTaskTraitsExtension");
+    return {kExtensionId,
+            {static_cast<uint8_t>(browser_thread_),
+             static_cast<uint8_t>(nestable_)}};
+  }
+
+  static const BrowserTaskTraitsExtension Parse(
+      const base::TaskTraitsExtensionStorage& extension) {
+    return BrowserTaskTraitsExtension(
+        static_cast<BrowserThread::ID>(extension.data[0]),
+        static_cast<bool>(extension.data[1]));
+  }
+
+  constexpr BrowserThread::ID browser_thread() const { return browser_thread_; }
+
+  // Returns true if tasks with these traits may run in a nested RunLoop.
+  constexpr bool nestable() const { return nestable_; }
+
+ private:
+  BrowserTaskTraitsExtension(BrowserThread::ID browser_thread, bool nestable)
+      : browser_thread_(browser_thread), nestable_(nestable) {}
+
+  BrowserThread::ID browser_thread_;
+  bool nestable_;
+};
+
+template <class... ArgTypes,
+          class = std::enable_if_t<base::trait_helpers::AreValidTraits<
+              BrowserTaskTraitsExtension::ValidTrait,
+              ArgTypes...>::value>>
+constexpr base::TaskTraitsExtensionStorage MakeTaskTraitsExtension(
+    ArgTypes&&... args) {
+  return BrowserTaskTraitsExtension(std::forward<ArgTypes>(args)...)
+      .Serialize();
+}
+
+}  // namespace content
+
+#endif  // CONTENT_PUBLIC_BROWSER_BROWSER_TASK_TRAITS_H_
diff --git a/content/public/browser/browser_thread.h b/content/public/browser/browser_thread.h
index 5d43972..159bc7f 100644
--- a/content/public/browser/browser_thread.h
+++ b/content/public/browser/browser_thread.h
@@ -18,7 +18,6 @@
 #include "base/task_runner_util.h"
 #include "base/time/time.h"
 #include "content/common/content_export.h"
-#include "content/public/browser/browser_thread.h"
 
 namespace content {
 
@@ -35,25 +34,17 @@
 ///////////////////////////////////////////////////////////////////////////////
 // BrowserThread
 //
-// Utility functions for threads that are known by a browser-wide
-// name.  For example, there is one IO thread for the entire browser
-// process, and various pieces of code find it useful to retrieve a
-// pointer to the IO thread's message loop.
+// Utility functions for threads that are known by a browser-wide name.  For
+// example, there is one IO thread for the entire browser process, and various
+// pieces of code find it useful to retrieve a pointer to the IO thread's
+// message loop.
 //
-// Invoke a task by thread ID:
+// See browser_task_traits.h for posting Tasks to a BrowserThread.
+// TODO(https://crbug.com/878356): Replace uses with base's post_task.h API.
 //
-//   BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, task);
-//
-// The return value is false if the task couldn't be posted because the target
-// thread doesn't exist.  If this could lead to data loss, you need to check the
-// result and restructure the code to ensure it doesn't occur.
-//
-// This class automatically handles the lifetime of different threads.
-// It's always safe to call PostTask on any thread.  If it's not yet created,
-// the task is deleted.  There are no race conditions.  If the thread that the
-// task is posted to is guaranteed to outlive the current thread, then no locks
-// are used.  You should never need to cache pointers to MessageLoops, since
-// they're not thread safe.
+// This class automatically handles the lifetime of different threads. You
+// should never need to cache pointers to MessageLoops, since they're not thread
+// safe.
 class CONTENT_EXPORT BrowserThread {
  public:
   // An enumeration of the well-known threads.
@@ -68,7 +59,7 @@
     IO,
 
     // NOTE: do not add new threads here. Instead you should just use
-    // base::Create*TaskRunnerWithTraits.
+    // base::Create*TaskRunnerWithTraits to run tasks on the TaskScheduler.
 
     // This identifier does not represent a thread.  Instead it counts the
     // number of well-known threads.  Insert new well-known threads before this
@@ -76,6 +67,9 @@
     ID_COUNT
   };
 
+  // DEPRECATED: Please use the API described in browser_task_traits.h instead.
+  // TODO(https://crbug.com/878356): Replace uses with base::PostTaskWithTraits.
+  //
   // These are the same methods in message_loop.h, but are guaranteed to either
   // get posted to the MessageLoop if it's still alive, or be deleted otherwise.
   // They return true iff the thread existed and the task was posted.  Note that
@@ -176,8 +170,12 @@
   // sets identifier to its ID.  Otherwise returns false.
   static bool GetCurrentThreadIdentifier(ID* identifier) WARN_UNUSED_RESULT;
 
-  // Callers can hold on to a refcounted task runner beyond the lifetime
-  // of the thread.
+  // DEPRECATED: Please use the API described in browser_task_traits.h instead.
+  // TODO(https://crbug.com/878356): Replace uses with
+  // base::Create*TaskRunnerWithTraits.
+  //
+  // Callers can hold on to a refcounted task runner beyond the lifetime of the
+  // thread.
   static scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunnerForThread(
       ID identifier);
 
diff --git a/content/public/test/browser_test_base.cc b/content/public/test/browser_test_base.cc
index 5b84ebd..cb5fcf4c 100644
--- a/content/public/test/browser_test_base.cc
+++ b/content/public/test/browser_test_base.cc
@@ -23,6 +23,7 @@
 #include "base/test/test_timeouts.h"
 #include "base/threading/thread_restrictions.h"
 #include "build/build_config.h"
+#include "content/browser/browser_thread_impl.h"
 #include "content/browser/renderer_host/render_process_host_impl.h"
 #include "content/browser/tracing/tracing_controller_impl.h"
 #include "content/public/app/content_main.h"
@@ -313,6 +314,7 @@
   params.ui_task = ui_task.release();
   params.created_main_parts_closure = created_main_parts_closure.release();
   base::TaskScheduler::Create("Browser");
+  BrowserThreadImpl::CreateTaskExecutor();
   // TODO(phajdan.jr): Check return code, http://crbug.com/374738 .
   BrowserMain(params);
 #else
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 31ea8cc..804367e 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -725,6 +725,7 @@
     "../browser/blob_storage/blob_url_browsertest.cc",
     "../browser/bookmarklet_browsertest.cc",
     "../browser/browser_side_navigation_browsertest.cc",
+    "../browser/browser_thread_browsertest.cc",
     "../browser/browsing_data/browsing_data_remover_impl_browsertest.cc",
     "../browser/browsing_data/clear_site_data_handler_browsertest.cc",
     "../browser/browsing_data/conditional_cache_deletion_helper_browsertest.cc",