diff --git a/DEPS b/DEPS
index 44025c2..834e405 100644
--- a/DEPS
+++ b/DEPS
@@ -126,11 +126,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'b00f7b34751b64a7dc3051e452f5a63afe222891',
+  'skia_revision': '7a74c7cb6da0809e2ea2121932b889844e249157',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '3c4ae81e70e9046ea501ff0ce7169cc33cb68ece',
+  'v8_revision': 'ad16165cca87b0bdaf39feb026844252fd3c724c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -138,15 +138,15 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '366df2b26dd1809be283beb01824ec0a9d899bd5',
+  'angle_revision': 'aead8edf8c46ace321f0a5583cea21194a77baf1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': 'de16f327d051dd5a33b3b7c4666feea071cd877a',
+  'swiftshader_revision': '2bb0864b22e70df9907bb71ca985a110f33d29da',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': '4679e62edbe5cf62286606e0fe3323d64faa97c2',
+  'pdfium_revision': '333ac4ae60ac700fe9089ffe60135b19984e0b89',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling openmax_dl
   # and whatever else without interference from each other.
@@ -189,7 +189,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '778eee07409eb2f2a5589c2bfdb050b61b60fde2',
+  'catapult_revision': '309c28a6328bf11bb2863afab0b28691a783941f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -253,7 +253,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '4dec7371a221faf30d037a6bca0618cb94ca4210',
+  'dawn_revision': '108bcbd5c9b83c545e6466d787035527a35f909f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -732,7 +732,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '25ca3f40d18c40de46f990f54413ddc4b0299f34',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'd70d87c663f37683f805eb5d8ffe5679d48acb72',
       'condition': 'checkout_linux',
   },
 
@@ -1255,7 +1255,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'a0f51b2e123f39c9ff12e621b0b47dd28dd64424',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'd036c6582e130eb558ac12b56ea2d86909100f6e',
+    Var('webrtc_git') + '/src.git' + '@' + '397c06fe9db8512e18ffa401ead1dd7cf4e725d0',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1296,7 +1296,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@9b5028a4743a553c981ae845b89f17604ee5a381',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@211efe6b022c41e74d7c740e11e635eee36e861d',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/browser/aw_settings.cc b/android_webview/browser/aw_settings.cc
index 7d17d42..af9f731 100644
--- a/android_webview/browser/aw_settings.cc
+++ b/android_webview/browser/aw_settings.cc
@@ -19,9 +19,9 @@
 #include "content/public/browser/navigation_controller.h"
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/render_view_host.h"
+#include "content/public/browser/renderer_preferences_util.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
-#include "content/public/common/renderer_preferences_util.h"
 #include "content/public/common/web_preferences.h"
 #include "jni/AwSettings_jni.h"
 #include "net/http/http_util.h"
@@ -233,6 +233,7 @@
 
   if (!renderer_prefs_initialized_) {
     content::UpdateFontRendererPreferencesFromSystemSettings(prefs);
+    content::UpdateFocusRingPreferencesFromSystemSettings(prefs);
     renderer_prefs_initialized_ = true;
     update_prefs = true;
   }
diff --git a/ash/public/interfaces/login_user_info.mojom b/ash/public/interfaces/login_user_info.mojom
index a58732b..16f8b69b 100644
--- a/ash/public/interfaces/login_user_info.mojom
+++ b/ash/public/interfaces/login_user_info.mojom
@@ -10,7 +10,7 @@
 import "mojo/public/mojom/base/values.mojom";
 
 // Supported multi-profile user behavior values.
-// Keep in sync with the enum in md_user_pod_row.js and user_pod_row.js
+// Keep in sync with the enum in chromeos_user_pod_row.js and user_pod_row.js
 enum MultiProfileUserBehavior {
   UNRESTRICTED = 0,
   PRIMARY_ONLY = 1,
diff --git a/ash/system/message_center/unified_message_center_view.cc b/ash/system/message_center/unified_message_center_view.cc
index d1cd29bd..58b9b256 100644
--- a/ash/system/message_center/unified_message_center_view.cc
+++ b/ash/system/message_center/unified_message_center_view.cc
@@ -320,6 +320,9 @@
 }
 
 void UnifiedMessageCenterView::NotifyRectBelowScroll() {
+  if (!visible())
+    return;
+
   gfx::Rect rect_below_scroll;
   rect_below_scroll.set_height(
       std::max(0, message_list_view_->GetLastNotificationBounds().bottom() -
diff --git a/ash/system/unified/unified_system_tray_view.cc b/ash/system/unified/unified_system_tray_view.cc
index 1a4b496..973c0c2 100644
--- a/ash/system/unified/unified_system_tray_view.cc
+++ b/ash/system/unified/unified_system_tray_view.cc
@@ -60,6 +60,7 @@
     flags.setAntiAlias(true);
 
     gfx::Rect rect = rect_below_scroll_;
+    rect.set_height(std::min(rect.height(), kUnifiedTrayCornerRadius * 2));
     rect.Inset(gfx::Insets(-kUnifiedTrayCornerRadius * 4, 0, 0, 0));
     canvas->DrawRoundRect(gfx::RectF(rect), kUnifiedTrayCornerRadius, flags);
   }
diff --git a/base/logging.cc b/base/logging.cc
index fb64437..58f3d15f 100644
--- a/base/logging.cc
+++ b/base/logging.cc
@@ -768,15 +768,21 @@
         priority = ANDROID_LOG_FATAL;
         break;
     }
+    const char kAndroidLogTag[] = "chromium";
 #if DCHECK_IS_ON()
     // Split the output by new lines to prevent the Android system from
     // truncating the log.
-    for (const auto& line : base::SplitString(
-             str_newline, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL))
-      __android_log_write(priority, "chromium", line.c_str());
+    std::vector<std::string> lines = base::SplitString(
+        str_newline, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+    // str_newline has an extra newline appended to it (at the top of this
+    // function), so skip the last split element to avoid needlessly
+    // logging an empty string.
+    lines.pop_back();
+    for (const auto& line : lines)
+      __android_log_write(priority, kAndroidLogTag, line.c_str());
 #else
     // The Android system may truncate the string if it's too long.
-    __android_log_write(priority, "chromium", str_newline.c_str());
+    __android_log_write(priority, kAndroidLogTag, str_newline.c_str());
 #endif
 #endif  // OS_ANDROID
     ignore_result(fwrite(str_newline.data(), str_newline.size(), 1, stderr));
diff --git a/base/task/sequence_manager/sequence_manager_impl_unittest.cc b/base/task/sequence_manager/sequence_manager_impl_unittest.cc
index 2fdb2bd..795ce042 100644
--- a/base/task/sequence_manager/sequence_manager_impl_unittest.cc
+++ b/base/task/sequence_manager/sequence_manager_impl_unittest.cc
@@ -8,6 +8,7 @@
 #include <memory>
 #include <utility>
 
+#include "base/auto_reset.h"
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/location.h"
@@ -18,6 +19,7 @@
 #include "base/optional.h"
 #include "base/run_loop.h"
 #include "base/single_thread_task_runner.h"
+#include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/task/sequence_manager/real_time_domain.h"
@@ -63,13 +65,34 @@
 // To avoid symbol collisions in jumbo builds.
 namespace sequence_manager_impl_unittest {
 
-enum class TestType : int {
-  kCustom = 0,
-  kUseMockTaskRunner = 1,
-  kUseMessageLoop = 2,
-  kUseMessagePump = 3,
+enum class TestType {
+  kCustom,
+  kMockTaskRunner,
+  kMessageLoop,
+  kMessagePump,
 };
 
+std::string ToString(TestType type) {
+  switch (type) {
+    case TestType::kMockTaskRunner:
+      return "kMockTaskRunner";
+    case TestType::kMessagePump:
+      return "kMessagePump";
+    case TestType::kMessageLoop:
+      return "kMessageLoop";
+    case TestType::kCustom:
+      return "kCustom";
+  }
+}
+
+std::string GetTestNameSuffix(const testing::TestParamInfo<TestType>& info) {
+  return StrCat({"With", ToString(info.param).substr(1)});
+}
+
+void PrintTo(const TestType type, std::ostream* os) {
+  *os << ToString(type);
+}
+
 using MockTask = MockCallback<base::RepeatingCallback<void()>>;
 
 // This class abstracts the details of how the SequenceManager runs tasks.
@@ -150,9 +173,9 @@
   std::unique_ptr<SequenceManagerForTest> sequence_manager_;
 };
 
-class FixtureWithWithMockMessagePump : public Fixture {
+class FixtureWithMockMessagePump : public Fixture {
  public:
-  FixtureWithWithMockMessagePump() {
+  FixtureWithMockMessagePump() {
     // A null clock triggers some assertions.
     mock_clock_.Advance(TimeDelta::FromMilliseconds(1));
 
@@ -211,6 +234,80 @@
   std::unique_ptr<SequenceManagerForTest> sequence_manager_;
 };
 
+class FixtureWithMessageLoop : public Fixture {
+ public:
+  FixtureWithMessageLoop()
+      : auto_reset_global_clock_(&global_clock_, &mock_clock_) {
+    // A null clock triggers some assertions.
+    mock_clock_.Advance(TimeDelta::FromMilliseconds(1));
+    scoped_clock_override_ =
+        std::make_unique<base::subtle::ScopedTimeClockOverrides>(
+            nullptr, TicksNowOverride, nullptr);
+
+    auto pump = std::make_unique<MockTimeMessagePump>(&mock_clock_);
+    pump_ = pump.get();
+    message_loop_ = std::make_unique<MessageLoop>(std::move(pump));
+
+    sequence_manager_ = SequenceManagerForTest::Create(
+        message_loop_->GetMessageLoopBase(), ThreadTaskRunnerHandle::Get(),
+        &mock_clock_,
+        SequenceManager::Settings{.randomised_sampling_enabled = false});
+  }
+
+  void AdvanceMockTickClock(TimeDelta delta) override {
+    mock_clock_.Advance(delta);
+  }
+
+  const TickClock* mock_tick_clock() const override { return &mock_clock_; }
+
+  TimeDelta NextPendingTaskDelay() const override {
+    return pump_->next_wake_up_time() - mock_clock_.NowTicks();
+  }
+
+  void FastForwardBy(TimeDelta delta) override {
+    pump_->SetAllowTimeToAutoAdvanceUntil(mock_clock_.NowTicks() + delta);
+    pump_->SetStopWhenMessagePumpIsIdle(true);
+    RunLoop().Run();
+    pump_->SetStopWhenMessagePumpIsIdle(false);
+  }
+
+  void FastForwardUntilNoTasksRemain() override {
+    pump_->SetAllowTimeToAutoAdvanceUntil(TimeTicks::Max());
+    pump_->SetStopWhenMessagePumpIsIdle(true);
+    RunLoop().Run();
+    pump_->SetStopWhenMessagePumpIsIdle(false);
+    pump_->SetAllowTimeToAutoAdvanceUntil(mock_clock_.NowTicks());
+  }
+
+  void RunDoWorkOnce() override {
+    pump_->SetQuitAfterDoSomeWork(true);
+    RunLoop().Run();
+    pump_->SetQuitAfterDoSomeWork(false);
+  }
+
+  SequenceManagerForTest* sequence_manager() const override {
+    return sequence_manager_.get();
+  }
+
+  void DestroySequenceManager() override {
+    pump_ = nullptr;
+    sequence_manager_.reset();
+  }
+
+ private:
+  static TickClock* global_clock_;
+  static TimeTicks TicksNowOverride() { return global_clock_->NowTicks(); }
+  SimpleTestTickClock mock_clock_;
+  AutoReset<TickClock*> auto_reset_global_clock_;
+  std::unique_ptr<base::subtle::ScopedTimeClockOverrides>
+      scoped_clock_override_;
+  std::unique_ptr<MessageLoop> message_loop_;
+  MockTimeMessagePump* pump_ = nullptr;
+  std::unique_ptr<SequenceManagerForTest> sequence_manager_;
+};
+
+TickClock* FixtureWithMessageLoop::global_clock_;
+
 // SequenceManagerImpl uses TestMockTimeTaskRunner which controls
 // both task execution and mock clock.
 // TODO(kraynov): Make this class to support all TestTypes.
@@ -221,14 +318,17 @@
  public:
   SequenceManagerTest() {
     switch (GetParam()) {
-      case TestType::kUseMockTaskRunner:
+      case TestType::kMockTaskRunner:
         fixture_ = std::make_unique<FixtureWithMockTaskRunner>();
         break;
-      case TestType::kUseMessagePump:
-        fixture_ = std::make_unique<FixtureWithWithMockMessagePump>();
+      case TestType::kMessagePump:
+        fixture_ = std::make_unique<FixtureWithMockMessagePump>();
+        break;
+      case TestType::kMessageLoop:
+        fixture_ = std::make_unique<FixtureWithMessageLoop>();
         break;
       default:
-        DCHECK(false);
+        NOTREACHED();
     }
   }
 
@@ -296,19 +396,6 @@
   std::unique_ptr<Fixture> fixture_;
 };
 
-std::string GetTestNameSuffix(const testing::TestParamInfo<TestType>& info) {
-  switch (info.param) {
-    case TestType::kUseMockTaskRunner:
-      return "MockTaskRunner";
-    case TestType::kUseMessagePump:
-      return "MessagePump";
-    case TestType::kUseMessageLoop:
-      return "MessageLoop";
-    case TestType::kCustom:
-      return "Custom";
-  }
-}
-
 // TODO(carlscab): Remove once the classes below have been migrated to
 // SequenceManagerTest
 class SequenceManagerTestBase : public testing::TestWithParam<TestType> {
@@ -343,46 +430,10 @@
 // TODO(kraynov): Generalize as many tests as possible to run it
 // in all supported environments.
 // TODO(carlscab): Migrate to SequenceManagerTest and remove
-class SequenceManagerTestWithMessageLoop : public SequenceManagerTestBase {
+class SequenceManagerTestWithCustomInitialization
+    : public SequenceManagerTestBase {
  protected:
-  void SetUp() override {
-    switch (GetParam()) {
-      case TestType::kUseMessageLoop:
-        SetUpWithMessageLoop();
-        break;
-      case TestType::kUseMessagePump:
-        SetUpWithMessagePump();
-        break;
-      default:
-        FAIL();
-    }
-  }
-
-  void SetUpWithMessageLoop() {
-    message_loop_.reset(new MessageLoop());
-    // A null clock triggers some assertions.
-    mock_clock_.Advance(TimeDelta::FromMilliseconds(1));
-    start_time_ = mock_clock_.NowTicks();
-
-    manager_ = SequenceManagerForTest::Create(
-        message_loop_->GetMessageLoopBase(), ThreadTaskRunnerHandle::Get(),
-        &mock_clock_,
-        SequenceManager::Settings{.randomised_sampling_enabled = false});
-  }
-
-  void SetUpWithMessagePump() {
-    mock_clock_.Advance(TimeDelta::FromMilliseconds(1));
-    start_time_ = mock_clock_.NowTicks();
-    manager_ = SequenceManagerForTest::Create(
-        std::make_unique<ThreadControllerWithMessagePumpImpl>(
-            std::make_unique<MessagePumpDefault>(), &mock_clock_),
-        SequenceManager::Settings{.randomised_sampling_enabled = false});
-    // ThreadControllerWithMessagePumpImpl doesn't provide
-    // a default task runner.
-    default_task_queue_ = manager_->CreateTaskQueueWithType<TestTaskQueue>(
-        TaskQueue::Spec("default"));
-    manager_->SetDefaultTaskRunner(default_task_queue_->task_runner());
-  }
+  void SetUp() override { ASSERT_EQ(GetParam(), TestType::kCustom); }
 
   const TickClock* GetTickClock() { return &mock_clock_; }
 
@@ -391,22 +442,12 @@
   SimpleTestTickClock mock_clock_;
 };
 
-// TODO(carlscab): Migrate to SequenceManagerTest and remove
-class SequenceManagerTestWithCustomInitialization
-    : public SequenceManagerTestWithMessageLoop {
- protected:
-  void SetUp() override { ASSERT_EQ(GetParam(), TestType::kCustom); }
-};
-
 INSTANTIATE_TEST_SUITE_P(,
                          SequenceManagerTest,
-                         testing::Values(TestType::kUseMockTaskRunner,
-                                         TestType::kUseMessagePump));
-
-INSTANTIATE_TEST_SUITE_P(,
-                         SequenceManagerTestWithMessageLoop,
-                         testing::Values(TestType::kUseMessageLoop,
-                                         TestType::kUseMessagePump));
+                         testing::Values(TestType::kMockTaskRunner,
+                                         TestType::kMessageLoop,
+                                         TestType::kMessagePump),
+                         GetTestNameSuffix);
 
 INSTANTIATE_TEST_SUITE_P(,
                          SequenceManagerTestWithCustomInitialization,
@@ -571,7 +612,7 @@
   EXPECT_THAT(run_order, ElementsAre(1u, 2u, 3u, 4u, 5u, 6u));
 }
 
-TEST_P(SequenceManagerTestWithMessageLoop, NonNestableTaskPosting) {
+TEST_P(SequenceManagerTest, NonNestableTaskPosting) {
   auto queue = CreateTaskQueue();
 
   std::vector<EnqueueOrder> run_order;
@@ -582,8 +623,7 @@
   EXPECT_THAT(run_order, ElementsAre(1u));
 }
 
-TEST_P(SequenceManagerTestWithMessageLoop,
-       NonNestableTaskExecutesInExpectedOrder) {
+TEST_P(SequenceManagerTest, NonNestableTaskExecutesInExpectedOrder) {
   auto queue = CreateTaskQueue();
 
   std::vector<EnqueueOrder> run_order;
@@ -598,8 +638,9 @@
   EXPECT_THAT(run_order, ElementsAre(1u, 2u, 3u, 4u, 5u));
 }
 
-TEST_P(SequenceManagerTestWithMessageLoop,
-       NonNestableTasksDoesntExecuteInNestedLoop) {
+TEST_P(SequenceManagerTest, NonNestableTasksDoesntExecuteInNestedLoop) {
+  if (GetParam() == TestType::kMockTaskRunner)
+    return;
   auto queue = CreateTaskQueue();
 
   std::vector<EnqueueOrder> run_order;
@@ -643,7 +684,9 @@
 
 }  // namespace
 
-TEST_P(SequenceManagerTestWithMessageLoop, TaskQueueDisabledFromNestedLoop) {
+TEST_P(SequenceManagerTest, TaskQueueDisabledFromNestedLoop) {
+  if (GetParam() == TestType::kMockTaskRunner)
+    return;
   auto queue = CreateTaskQueue();
   std::vector<EnqueueOrder> run_order;
 
@@ -653,7 +696,7 @@
       std::make_pair(BindOnce(&TestTask, 1, &run_order), false));
   tasks_to_post_from_nested_loop.push_back(
       std::make_pair(BindOnce(&InsertFenceAndPostTestTask, 2, &run_order, queue,
-                              manager_.get()),
+                              sequence_manager()),
                      true));
 
   queue->task_runner()->PostTask(
@@ -1385,7 +1428,7 @@
   DestroySequenceManager();
   queue->task_runner()->PostTask(FROM_HERE, counter.WrapCallback(task.Get()));
 
-  if (GetParam() != TestType::kUseMessagePump) {
+  if (GetParam() != TestType::kMessagePump) {
     RunLoop().RunUntilIdle();
   }
 
@@ -1397,7 +1440,7 @@
   runner->task_runner()->PostTask(FROM_HERE, BindOnce(&TestTask, 1, run_order));
 }
 
-TEST_P(SequenceManagerTestWithMessageLoop, PostFromThread) {
+TEST_P(SequenceManagerTest, PostFromThread) {
   auto queue = CreateTaskQueue();
 
   std::vector<EnqueueOrder> run_order;
@@ -1430,7 +1473,7 @@
   EXPECT_EQ(1, run_count);
 }
 
-TEST_P(SequenceManagerTestWithMessageLoop, PostFromNestedRunloop) {
+TEST_P(SequenceManagerTest, PostFromNestedRunloop) {
   auto queue = CreateTaskQueue();
 
   std::vector<EnqueueOrder> run_order;
@@ -1475,12 +1518,12 @@
   MOCK_METHOD1(WillProcessTask, void(const PendingTask& task));
 };
 
-TEST_P(SequenceManagerTestWithMessageLoop, TaskObserverAdding) {
+TEST_P(SequenceManagerTest, TaskObserverAdding) {
   auto queue = CreateTaskQueue();
   MockTaskObserver observer;
 
-  manager_->SetWorkBatchSize(2);
-  manager_->AddTaskObserver(&observer);
+  sequence_manager()->SetWorkBatchSize(2);
+  sequence_manager()->AddTaskObserver(&observer);
 
   std::vector<EnqueueOrder> run_order;
   queue->task_runner()->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
@@ -1491,12 +1534,12 @@
   RunLoop().RunUntilIdle();
 }
 
-TEST_P(SequenceManagerTestWithMessageLoop, TaskObserverRemoving) {
+TEST_P(SequenceManagerTest, TaskObserverRemoving) {
   auto queue = CreateTaskQueue();
   MockTaskObserver observer;
-  manager_->SetWorkBatchSize(2);
-  manager_->AddTaskObserver(&observer);
-  manager_->RemoveTaskObserver(&observer);
+  sequence_manager()->SetWorkBatchSize(2);
+  sequence_manager()->AddTaskObserver(&observer);
+  sequence_manager()->RemoveTaskObserver(&observer);
 
   std::vector<EnqueueOrder> run_order;
   queue->task_runner()->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
@@ -1511,25 +1554,25 @@
   manager->RemoveTaskObserver(observer);
 }
 
-TEST_P(SequenceManagerTestWithMessageLoop, TaskObserverRemovingInsideTask) {
+TEST_P(SequenceManagerTest, TaskObserverRemovingInsideTask) {
   auto queue = CreateTaskQueue();
   MockTaskObserver observer;
-  manager_->SetWorkBatchSize(3);
-  manager_->AddTaskObserver(&observer);
+  sequence_manager()->SetWorkBatchSize(3);
+  sequence_manager()->AddTaskObserver(&observer);
 
   queue->task_runner()->PostTask(
-      FROM_HERE, BindOnce(&RemoveObserverTask, manager_.get(), &observer));
+      FROM_HERE, BindOnce(&RemoveObserverTask, sequence_manager(), &observer));
 
   EXPECT_CALL(observer, WillProcessTask(_)).Times(1);
   EXPECT_CALL(observer, DidProcessTask(_)).Times(0);
   RunLoop().RunUntilIdle();
 }
 
-TEST_P(SequenceManagerTestWithMessageLoop, QueueTaskObserverAdding) {
-  auto queues = CreateTaskQueues(2u);
+TEST_P(SequenceManagerTest, QueueTaskObserverAdding) {
+  auto queues = CreateTaskQueues(2);
   MockTaskObserver observer;
 
-  manager_->SetWorkBatchSize(2);
+  sequence_manager()->SetWorkBatchSize(2);
   queues[0]->AddTaskObserver(&observer);
 
   std::vector<EnqueueOrder> run_order;
@@ -1543,10 +1586,10 @@
   RunLoop().RunUntilIdle();
 }
 
-TEST_P(SequenceManagerTestWithMessageLoop, QueueTaskObserverRemoving) {
+TEST_P(SequenceManagerTest, QueueTaskObserverRemoving) {
   auto queue = CreateTaskQueue();
   MockTaskObserver observer;
-  manager_->SetWorkBatchSize(2);
+  sequence_manager()->SetWorkBatchSize(2);
   queue->AddTaskObserver(&observer);
   queue->RemoveTaskObserver(&observer);
 
@@ -1564,8 +1607,7 @@
   queue->RemoveTaskObserver(observer);
 }
 
-TEST_P(SequenceManagerTestWithMessageLoop,
-       QueueTaskObserverRemovingInsideTask) {
+TEST_P(SequenceManagerTest, QueueTaskObserverRemovingInsideTask) {
   auto queue = CreateTaskQueue();
   MockTaskObserver observer;
   queue->AddTaskObserver(&observer);
@@ -1836,11 +1878,13 @@
   run_loop->Run();
 }
 
-TEST_P(SequenceManagerTestWithMessageLoop, QuitWhileNested) {
+TEST_P(SequenceManagerTest, QuitWhileNested) {
+  if (GetParam() == TestType::kMockTaskRunner)
+    return;
   // This test makes sure we don't continue running a work batch after a nested
   // run loop has been exited in the middle of the batch.
   auto queue = CreateTaskQueue();
-  manager_->SetWorkBatchSize(2);
+  sequence_manager()->SetWorkBatchSize(2);
 
   bool was_nested = true;
   RunLoop run_loop(RunLoop::Type::kNestableTasksAllowed);
@@ -2020,7 +2064,7 @@
 
 }  // namespace
 
-TEST_P(SequenceManagerTestWithMessageLoop, ShutdownTaskQueueInNestedLoop) {
+TEST_P(SequenceManagerTest, ShutdownTaskQueueInNestedLoop) {
   auto queue = CreateTaskQueue();
 
   // We retain a reference to the task queue even when the manager has deleted
@@ -2701,8 +2745,7 @@
 }
 }  // namespace
 
-TEST_P(SequenceManagerTestWithMessageLoop,
-       CurrentlyExecutingTaskQueue_NestedLoop) {
+TEST_P(SequenceManagerTest, CurrentlyExecutingTaskQueue_NestedLoop) {
   auto queues = CreateTaskQueues(3u);
 
   TestTaskQueue* queue0 = queues[0].get();
@@ -2714,27 +2757,29 @@
       tasks_to_post_from_nested_loop;
   tasks_to_post_from_nested_loop.push_back(
       std::make_pair(BindOnce(&CurrentlyExecutingTaskQueueTestTask,
-                              manager_.get(), &task_sources),
+                              sequence_manager(), &task_sources),
                      queue1));
   tasks_to_post_from_nested_loop.push_back(
       std::make_pair(BindOnce(&CurrentlyExecutingTaskQueueTestTask,
-                              manager_.get(), &task_sources),
+                              sequence_manager(), &task_sources),
                      queue2));
 
   queue0->task_runner()->PostTask(
       FROM_HERE,
-      BindOnce(&RunloopCurrentlyExecutingTaskQueueTestTask, manager_.get(),
+      BindOnce(&RunloopCurrentlyExecutingTaskQueueTestTask, sequence_manager(),
                &task_sources, &tasks_to_post_from_nested_loop));
 
   RunLoop().RunUntilIdle();
-  EXPECT_THAT(
-      task_sources,
-      ElementsAre(queue0->GetTaskQueueImpl(), queue1->GetTaskQueueImpl(),
-                  queue2->GetTaskQueueImpl(), queue0->GetTaskQueueImpl()));
-  EXPECT_EQ(nullptr, manager_->currently_executing_task_queue());
+  EXPECT_THAT(task_sources, UnorderedElementsAre(queue0->GetTaskQueueImpl(),
+                                                 queue1->GetTaskQueueImpl(),
+                                                 queue2->GetTaskQueueImpl(),
+                                                 queue0->GetTaskQueueImpl()));
+  EXPECT_EQ(nullptr, sequence_manager()->currently_executing_task_queue());
 }
 
-TEST_P(SequenceManagerTestWithMessageLoop, BlameContextAttribution) {
+TEST_P(SequenceManagerTest, BlameContextAttribution) {
+  if (GetParam() == TestType::kMessagePump)
+    return;
   using trace_analyzer::Query;
 
   auto queue = CreateTaskQueue();
@@ -3153,22 +3198,24 @@
 }
 
 namespace {
-void MessageLoopTaskWithDelayedQuit(SimpleTestTickClock* now_src,
+void MessageLoopTaskWithDelayedQuit(Fixture* fixture,
                                     scoped_refptr<TestTaskQueue> task_queue) {
   RunLoop run_loop(RunLoop::Type::kNestableTasksAllowed);
   task_queue->task_runner()->PostDelayedTask(FROM_HERE, run_loop.QuitClosure(),
                                              TimeDelta::FromMilliseconds(100));
-  now_src->Advance(TimeDelta::FromMilliseconds(200));
+  fixture->AdvanceMockTickClock(TimeDelta::FromMilliseconds(200));
   run_loop.Run();
 }
 }  // namespace
 
-TEST_P(SequenceManagerTestWithMessageLoop, DelayedTaskRunsInNestedMessageLoop) {
+TEST_P(SequenceManagerTest, DelayedTaskRunsInNestedMessageLoop) {
+  if (GetParam() == TestType::kMockTaskRunner)
+    return;
   auto queue = CreateTaskQueue();
   RunLoop run_loop;
   queue->task_runner()->PostTask(
-      FROM_HERE, BindOnce(&MessageLoopTaskWithDelayedQuit, &mock_clock_,
-                          RetainedRef(queue)));
+      FROM_HERE,
+      BindOnce(&MessageLoopTaskWithDelayedQuit, this, RetainedRef(queue)));
   run_loop.RunUntilIdle();
 }
 
@@ -3185,8 +3232,9 @@
 }
 }  // namespace
 
-TEST_P(SequenceManagerTestWithMessageLoop,
-       DelayedNestedMessageLoopDoesntPreventTasksRunning) {
+TEST_P(SequenceManagerTest, DelayedNestedMessageLoopDoesntPreventTasksRunning) {
+  if (GetParam() == TestType::kMockTaskRunner)
+    return;
   auto queue = CreateTaskQueue();
   RunLoop run_loop;
   queue->task_runner()->PostDelayedTask(
@@ -3195,7 +3243,7 @@
                RetainedRef(queue)),
       TimeDelta::FromMilliseconds(100));
 
-  mock_clock_.Advance(TimeDelta::FromMilliseconds(200));
+  AdvanceMockTickClock(TimeDelta::FromMilliseconds(200));
   run_loop.Run();
 }
 
@@ -3252,9 +3300,23 @@
   std::unique_ptr<TaskQueue::QueueEnabledVoter> voter =
       queue->CreateQueueEnabledVoter();
   voter->SetQueueEnabled(false);
-  EXPECT_EQ(GetParam() == TestType::kUseMessagePump ? TimeDelta::FromDays(1)
-                                                    : TimeDelta::Max(),
-            NextPendingTaskDelay());
+
+  switch (GetParam()) {
+    case TestType::kMessagePump:
+      EXPECT_EQ(TimeDelta::FromDays(1), NextPendingTaskDelay());
+      break;
+
+    case TestType::kMessageLoop:
+      EXPECT_EQ(TimeDelta::FromMilliseconds(1), NextPendingTaskDelay());
+      break;
+
+    case TestType::kMockTaskRunner:
+      EXPECT_EQ(TimeDelta::Max(), NextPendingTaskDelay());
+      break;
+
+    default:
+      NOTREACHED();
+  }
 
   voter->SetQueueEnabled(true);
   EXPECT_EQ(TimeDelta::FromMilliseconds(1), NextPendingTaskDelay());
@@ -3279,15 +3341,36 @@
   EXPECT_EQ(TimeDelta::FromMilliseconds(1), NextPendingTaskDelay());
 
   voter0->SetQueueEnabled(false);
-  EXPECT_EQ(TimeDelta::FromMilliseconds(10), NextPendingTaskDelay());
+  if (GetParam() == TestType::kMessageLoop) {
+    EXPECT_EQ(TimeDelta::FromMilliseconds(1), NextPendingTaskDelay());
+  } else {
+    EXPECT_EQ(TimeDelta::FromMilliseconds(10), NextPendingTaskDelay());
+  }
 
   voter1->SetQueueEnabled(false);
-  EXPECT_EQ(TimeDelta::FromMilliseconds(100), NextPendingTaskDelay());
+  if (GetParam() == TestType::kMessageLoop) {
+    EXPECT_EQ(TimeDelta::FromMilliseconds(1), NextPendingTaskDelay());
+  } else {
+    EXPECT_EQ(TimeDelta::FromMilliseconds(100), NextPendingTaskDelay());
+  }
 
   voter2->SetQueueEnabled(false);
-  EXPECT_EQ(GetParam() == TestType::kUseMessagePump ? TimeDelta::FromDays(1)
-                                                    : TimeDelta::Max(),
-            NextPendingTaskDelay());
+  switch (GetParam()) {
+    case TestType::kMessagePump:
+      EXPECT_EQ(TimeDelta::FromDays(1), NextPendingTaskDelay());
+      break;
+
+    case TestType::kMessageLoop:
+      EXPECT_EQ(TimeDelta::FromMilliseconds(1), NextPendingTaskDelay());
+      break;
+
+    case TestType::kMockTaskRunner:
+      EXPECT_EQ(TimeDelta::Max(), NextPendingTaskDelay());
+      break;
+
+    default:
+      NOTREACHED();
+  }
 }
 
 TEST_P(SequenceManagerTest, GetNextScheduledWakeUp) {
@@ -3600,7 +3683,8 @@
   // thread.
   DestroySequenceManager();
 
-  if (GetParam() != TestType::kUseMessagePump) {
+  if (GetParam() != TestType::kMessagePump &&
+      GetParam() != TestType::kMessageLoop) {
     FastForwardUntilNoTasksRemain();
   }
 
@@ -3643,7 +3727,8 @@
   // Ensure that all queues-to-gracefully-shutdown are properly unregistered.
   DestroySequenceManager();
 
-  if (GetParam() != TestType::kUseMessagePump) {
+  if (GetParam() != TestType::kMessagePump &&
+      GetParam() != TestType::kMessageLoop) {
     FastForwardUntilNoTasksRemain();
   }
 
@@ -4174,13 +4259,13 @@
   EXPECT_TRUE(destruction_observer_called);
 }
 
-TEST_P(SequenceManagerTestWithMessageLoop, GetMessagePump) {
+TEST_P(SequenceManagerTest, GetMessagePump) {
   switch (GetParam()) {
     default:
-      EXPECT_THAT(manager_->GetMessagePump(), testing::IsNull());
+      EXPECT_THAT(sequence_manager()->GetMessagePump(), testing::IsNull());
       break;
-    case TestType::kUseMessagePump:
-      EXPECT_THAT(manager_->GetMessagePump(), testing::NotNull());
+    case TestType::kMessagePump:
+      EXPECT_THAT(sequence_manager()->GetMessagePump(), testing::NotNull());
       break;
   }
 }
@@ -4215,8 +4300,8 @@
 
 }  // namespace
 
-TEST_P(SequenceManagerTestWithMessageLoop, OnSystemIdleTimeDomainNotification) {
-  if (GetParam() != TestType::kUseMessagePump)
+TEST_P(SequenceManagerTest, OnSystemIdleTimeDomainNotification) {
+  if (GetParam() != TestType::kMessagePump)
     return;
 
   auto queue = CreateTaskQueue();
@@ -4225,11 +4310,11 @@
   // to MaybeFastForwardToNextTask.  If no run loop has requested quit on idle,
   // the parameter passed in should be false.
   StrictMock<MockTimeDomain> mock_time_domain;
-  manager_->RegisterTimeDomain(&mock_time_domain);
+  sequence_manager()->RegisterTimeDomain(&mock_time_domain);
   EXPECT_CALL(mock_time_domain, MaybeFastForwardToNextTask(false))
       .WillOnce(Return(false));
-  manager_->OnSystemIdle();
-  manager_->UnregisterTimeDomain(&mock_time_domain);
+  sequence_manager()->OnSystemIdle();
+  sequence_manager()->UnregisterTimeDomain(&mock_time_domain);
   Mock::VerifyAndClearExpectations(&mock_time_domain);
 
   // However if RunUntilIdle is called it should be true.
@@ -4238,9 +4323,9 @@
         StrictMock<MockTimeDomain> mock_time_domain;
         EXPECT_CALL(mock_time_domain, MaybeFastForwardToNextTask(true))
             .WillOnce(Return(false));
-        manager_->RegisterTimeDomain(&mock_time_domain);
-        manager_->OnSystemIdle();
-        manager_->UnregisterTimeDomain(&mock_time_domain);
+        sequence_manager()->RegisterTimeDomain(&mock_time_domain);
+        sequence_manager()->OnSystemIdle();
+        sequence_manager()->UnregisterTimeDomain(&mock_time_domain);
       }));
 
   RunLoop().RunUntilIdle();
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 4414659..936b1bb 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-688f2ce0afdcc2bb7537b99790ef273c65eeefd2
\ No newline at end of file
+3b01f76cec3e1be290b0cff261ebd1bcb429e61d
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 6f2664af..03c66092 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-4a90784d62733367a977d7c65593b3aef019a741
\ No newline at end of file
+2f2a8092ed00985040b8635d51324ef19791f342
\ No newline at end of file
diff --git a/chrome/VERSION b/chrome/VERSION
index a3beede..b1489d7 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=74
 MINOR=0
-BUILD=3706
+BUILD=3707
 PATCH=0
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
index 0ebec871..3b95b179 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
@@ -688,6 +688,7 @@
     @Override
     public void onBottomControlsHeightChanged(int bottomControlsHeight) {
         if (mTabVisible == null) return;
+        mTabVisible.setBottomControlsHeight(bottomControlsHeight);
         Point viewportSize = getViewportSize();
         setSize(mTabVisible.getWebContents(), mTabVisible.getContentView(), viewportSize.x,
                 viewportSize.y);
@@ -696,6 +697,7 @@
     @Override
     public void onTopControlsHeightChanged(int topControlsHeight, boolean controlsResizeView) {
         if (mTabVisible == null) return;
+        mTabVisible.setTopControlsHeight(topControlsHeight, controlsResizeView);
         Point viewportSize = getViewportSize();
         setSize(mTabVisible.getWebContents(), mTabVisible.getContentView(), viewportSize.x,
                 viewportSize.y);
@@ -1074,6 +1076,8 @@
                     webContents, mCompositorView.getWidth(), mCompositorView.getHeight());
         }
         if (tab.getView() == null) return;
+        tab.setTopControlsHeight(getTopControlsHeightPixels(), controlsResizeView());
+        tab.setBottomControlsHeight(getBottomControlsHeightPixels());
 
         // TextView with compound drawables in the NTP gets a wrong width when measure/layout is
         // performed in the unattached state. Delay the layout till #onLayoutChange().
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/ChromeFullscreenManager.java b/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/ChromeFullscreenManager.java
index 246f67b..4e87d81 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/ChromeFullscreenManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/ChromeFullscreenManager.java
@@ -498,6 +498,10 @@
         }
 
         mControlsResizeView = controlsResizeView;
+        Tab tab = getTab();
+        if (tab == null) return;
+        tab.setTopControlsHeight(getTopControlsHeight(), controlsResizeView);
+        tab.setBottomControlsHeight(getBottomControlsHeight());
         for (FullscreenListener listener : mListeners) listener.onUpdateViewportSize();
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
index 0de0d97..3debaed 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
@@ -354,6 +354,10 @@
     /** Whether or not the tab closing the tab can send the user back to the app that opened it. */
     private boolean mIsAllowedToReturnToExternalApp;
 
+    private int mTopControlsHeight;
+    private int mBottomControlsHeight;
+    private boolean mControlsResizeView;
+
     /**
      * The publisher URL for pages hosted on a trusted CDN, or null otherwise.
      */
@@ -2367,6 +2371,27 @@
         return constraints;
     }
 
+    public void setTopControlsHeight(int height, boolean controlsResizeView) {
+        mTopControlsHeight = height;
+        mControlsResizeView = controlsResizeView;
+    }
+
+    public void setBottomControlsHeight(int height) {
+        mBottomControlsHeight = height;
+    }
+
+    int getTopControlsHeight() {
+        return mTopControlsHeight;
+    }
+
+    int getBottomControlsHeight() {
+        return mBottomControlsHeight;
+    }
+
+    boolean controlsResizeView() {
+        return mControlsResizeView;
+    }
+
     /**
      * @param manager The fullscreen manager that should be notified of changes to this tab (if
      *                set to null, no more updates will come from this tab).
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroid.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroid.java
index f0fb9e0..fe3fa515 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroid.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroid.java
@@ -34,8 +34,6 @@
 import org.chromium.chrome.browser.document.DocumentWebContentsDelegate;
 import org.chromium.chrome.browser.findinpage.FindMatchRectsDetails;
 import org.chromium.chrome.browser.findinpage.FindNotificationDetails;
-import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
-import org.chromium.chrome.browser.fullscreen.FullscreenManager;
 import org.chromium.chrome.browser.fullscreen.FullscreenOptions;
 import org.chromium.chrome.browser.media.MediaCaptureNotificationService;
 import org.chromium.chrome.browser.policy.PolicyAuditor;
@@ -525,20 +523,17 @@
 
     @Override
     public int getTopControlsHeight() {
-        FullscreenManager manager = mTab.getFullscreenManager();
-        return manager != null ? manager.getTopControlsHeight() : 0;
+        return mTab.getTopControlsHeight();
     }
 
     @Override
     public int getBottomControlsHeight() {
-        FullscreenManager manager = mTab.getFullscreenManager();
-        return manager != null ? manager.getBottomControlsHeight() : 0;
+        return mTab.getBottomControlsHeight();
     }
 
     @Override
     public boolean controlsResizeView() {
-        FullscreenManager manager = mTab.getFullscreenManager();
-        return manager != null ? ((ChromeFullscreenManager) manager).controlsResizeView() : false;
+        return mTab.controlsResizeView();
     }
 
     /**
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index db8e4de7..0c43c470 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-74.0.3705.0_rc-r1-merged.afdo.bz2
\ No newline at end of file
+chromeos-chrome-amd64-74.0.3706.0_rc-r1-merged.afdo.bz2
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index 974dbfa..97771c1 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -3542,6 +3542,32 @@
     No description is available.
   </message>
 
+  <!-- Crostini export and import -->
+  <message name="IDS_CROSTINI_EXPORT_TITLE" desc="Title for exporting (backing up) the crostini container.">
+    Backup
+  </message>
+  <message name="IDS_CROSTINI_EXPORT_NOTIFICATION_IN_PROGRESS" desc="Message displayed while export is in progress in the notification for exporting (backing up) the crostini container.">
+    Backup up...
+  </message>
+  <message name="IDS_CROSTINI_EXPORT_NOTIFICATION_DONE" desc="Message displayed when done in the notification for exporting (backing up) the crostini container.">
+    Backup complete
+  </message>
+  <message name="IDS_CROSTINI_EXPORT_NOTIFICATION_FAILED" desc="Message displayed when there is a failure in the notification for exporting (backing up) the crostini container.">
+    Backup failed
+  </message>
+  <message name="IDS_CROSTINI_IMPORT_TITLE" desc="Title for importing (restoring) the crostini container.">
+    Restore
+  </message>
+  <message name="IDS_CROSTINI_IMPORT_NOTIFICATION_IN_PROGRESS" desc="Message displayed while import is in progress in the notification for importing (restoring) the crostini container.">
+    Restoring...
+  </message>
+  <message name="IDS_CROSTINI_IMPORT_NOTIFICATION_DONE" desc="Message displayed when done in the notification for importing (restoring) the crostini container.">
+    Restore complete
+  </message>
+  <message name="IDS_CROSTINI_IMPORT_NOTIFICATION_FAILED" desc="Message displayed when there is a failure in the notification for importing (restoring) the crostini container.">
+    Restore failed
+  </message>
+
   <!-- Time limit notification -->
   <message name="IDS_SCREEN_TIME_NOTIFICATION_TITLE" desc="The title of the notification when screen usage limit reaches before locking the device.">
     Almost time for a break
diff --git a/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_EXPORT_NOTIFICATION_DONE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_EXPORT_NOTIFICATION_DONE.png.sha1
new file mode 100644
index 0000000..afd7bd82
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_EXPORT_NOTIFICATION_DONE.png.sha1
@@ -0,0 +1 @@
+f4d76e1c540780ba80e4b6568367695c93d73f68
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_EXPORT_NOTIFICATION_FAILED.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_EXPORT_NOTIFICATION_FAILED.png.sha1
new file mode 100644
index 0000000..afd7bd82
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_EXPORT_NOTIFICATION_FAILED.png.sha1
@@ -0,0 +1 @@
+f4d76e1c540780ba80e4b6568367695c93d73f68
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_EXPORT_NOTIFICATION_IN_PROGRESS.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_EXPORT_NOTIFICATION_IN_PROGRESS.png.sha1
new file mode 100644
index 0000000..afd7bd82
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_EXPORT_NOTIFICATION_IN_PROGRESS.png.sha1
@@ -0,0 +1 @@
+f4d76e1c540780ba80e4b6568367695c93d73f68
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_EXPORT_TITLE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_EXPORT_TITLE.png.sha1
new file mode 100644
index 0000000..afd7bd82
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_EXPORT_TITLE.png.sha1
@@ -0,0 +1 @@
+f4d76e1c540780ba80e4b6568367695c93d73f68
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_IMPORT_NOTIFICATION_DONE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_IMPORT_NOTIFICATION_DONE.png.sha1
new file mode 100644
index 0000000..bceb036
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_IMPORT_NOTIFICATION_DONE.png.sha1
@@ -0,0 +1 @@
+67a7f5b5f40ee10862fdb1ce486f99f99812a5cf
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_IMPORT_NOTIFICATION_FAILED.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_IMPORT_NOTIFICATION_FAILED.png.sha1
new file mode 100644
index 0000000..bceb036
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_IMPORT_NOTIFICATION_FAILED.png.sha1
@@ -0,0 +1 @@
+67a7f5b5f40ee10862fdb1ce486f99f99812a5cf
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_IMPORT_NOTIFICATION_IN_PROGRESS.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_IMPORT_NOTIFICATION_IN_PROGRESS.png.sha1
new file mode 100644
index 0000000..bceb036
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_IMPORT_NOTIFICATION_IN_PROGRESS.png.sha1
@@ -0,0 +1 @@
+67a7f5b5f40ee10862fdb1ce486f99f99812a5cf
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_IMPORT_TITLE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_IMPORT_TITLE.png.sha1
new file mode 100644
index 0000000..bceb036
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_CROSTINI_IMPORT_TITLE.png.sha1
@@ -0,0 +1 @@
+67a7f5b5f40ee10862fdb1ce486f99f99812a5cf
\ No newline at end of file
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index d31b79b..f4e05359 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -5688,6 +5688,9 @@
       <message name="IDS_PICTURE_IN_PICTURE_NEXT_TRACK_CONTROL_ACCESSIBLE_TEXT" desc="Accessible text label used for the controls button in the Picture-in-Picture window. The button invokes next track action.">
         Next track
       </message>
+      <message name="IDS_PICTURE_IN_PICTURE_PREVIOUS_TRACK_CONTROL_ACCESSIBLE_TEXT" desc="Accessible text label used for the controls button in the Picture-in-Picture window. The button invokes previous track action.">
+        Previous track
+      </message>
       <message name="IDS_PICTURE_IN_PICTURE_CONFIRM_CLOSE_TITLE" desc="Text label of the title for the confirmation dialog. This dialog appears when the user tries to close a tab / window while in Picture-in-Picture mode.">
         Are you sure you want to close this tab?
       </message>
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index d685680a..d4226ab 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -521,6 +521,21 @@
     <message name="IDS_SETTINGS_CROSTINI_SHARED_PATHS_REMOVE_SHARING" desc="Tooltip to show when hovering on the remove icon for a crostini shared folder.">
       Remove sharing
     </message>
+    <message name="IDS_SETTINGS_CROSTINI_EXPORT_IMPORT_TITLE" desc="Title for crostini container export and imoprt (backup and restore) section">
+      Backup &amp; restore
+    </message>
+    <message name="IDS_SETTINGS_CROSTINI_EXPORT" desc="Tile for exporting (backing up) the crostini container.">
+      Backup
+    </message>
+    <message name="IDS_SETTINGS_CROSTINI_EXPORT_LABEL" desc="Description shown next to the button to export (backup) Crostini.">
+      Backup Linux apps and files
+    </message>
+    <message name="IDS_SETTINGS_CROSTINI_IMPORT" desc="Title for importing (restoring) the crostini container.">
+      Restore
+    </message>
+    <message name="IDS_SETTINGS_CROSTINI_IMPORT_LABEL" desc="Description shown next to the button to import (restore) Crostini.">
+      Replace your Linux apps and files with a previous backup
+    </message>
     <message name="IDS_SETTINGS_CROSTINI_SHARED_USB_DEVICES_LABEL" desc="Label for managing shared USB devices.">
       USB Device preferences
     </message>
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_EXPORT.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_EXPORT.png.sha1
new file mode 100644
index 0000000..e81d0cb
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_EXPORT.png.sha1
@@ -0,0 +1 @@
+15fd0f16293c47a24987f389ceb4a76dbb775206
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_EXPORT_IMPORT_TITLE.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_EXPORT_IMPORT_TITLE.png.sha1
new file mode 100644
index 0000000..e81d0cb
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_EXPORT_IMPORT_TITLE.png.sha1
@@ -0,0 +1 @@
+15fd0f16293c47a24987f389ceb4a76dbb775206
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_EXPORT_LABEL.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_EXPORT_LABEL.png.sha1
new file mode 100644
index 0000000..e81d0cb
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_EXPORT_LABEL.png.sha1
@@ -0,0 +1 @@
+15fd0f16293c47a24987f389ceb4a76dbb775206
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_IMPORT.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_IMPORT.png.sha1
new file mode 100644
index 0000000..e81d0cb
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_IMPORT.png.sha1
@@ -0,0 +1 @@
+15fd0f16293c47a24987f389ceb4a76dbb775206
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_IMPORT_LABEL.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_IMPORT_LABEL.png.sha1
new file mode 100644
index 0000000..e81d0cb
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_CROSTINI_IMPORT_LABEL.png.sha1
@@ -0,0 +1 @@
+15fd0f16293c47a24987f389ceb4a76dbb775206
\ No newline at end of file
diff --git a/chrome/app/vector_icons/BUILD.gn b/chrome/app/vector_icons/BUILD.gn
index 46462167..a5663fe 100644
--- a/chrome/app/vector_icons/BUILD.gn
+++ b/chrome/app/vector_icons/BUILD.gn
@@ -58,6 +58,7 @@
     "google_pay_logo.icon",
     "horizontal_menu.icon",
     "incognito.icon",
+    "incognito_profile.icon",
     "input.icon",
     "key.icon",
     "laptop.icon",
diff --git a/chrome/app/vector_icons/incognito_profile.icon b/chrome/app/vector_icons/incognito_profile.icon
new file mode 100644
index 0000000..060d9f7
--- /dev/null
+++ b/chrome/app/vector_icons/incognito_profile.icon
@@ -0,0 +1,48 @@
+// Copyright 2019 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.
+// This icon has a bigger circle to figure ratio in compare to incognito.icon.
+
+CANVAS_DIMENSIONS, 40,
+CIRCLE, 20, 20, 20,
+MOVE_TO, 16.51f, 11.29f,
+R_CUBIC_TO, 0.26f, 0.13f, 2.39f, 0.49f, 2.39f, 0.49f,
+R_CUBIC_TO, 0, 0, 4.68f, -0.68f, 4.85f, -0.72f,
+R_CUBIC_TO, 0.11f, -0.03f, 0.16f, 0.03f, 0.19f, 0.11f,
+R_CUBIC_TO, 0.01f, 0.05f, 0.81f, 2.65f, 1.48f, 4.83f,
+R_H_LINE_TO, -10.82f,
+R_ARC_TO, 951.85f, 951.85f, 0, 0, 0, 1.54f, -4.61f,
+R_CUBIC_TO, 0.04f, -0.14f, 0.18f, -0.19f, 0.38f, -0.1f,
+CLOSE,
+MOVE_TO, 23.84f, 27.05f,
+R_CUBIC_TO, -1.7f, 0, -3.08f, -1.37f, -3.08f, -3.05f,
+R_CUBIC_TO, 0, -0.09f, 0, -0.18f, 0.01f, -0.27f,
+R_ARC_TO, 2.63f, 2.63f, 0, 0, 0, -1.64f, 0.01f,
+R_CUBIC_TO, 0.01f, 0.08f, 0.01f, 0.17f, 0.01f, 0.25f,
+R_CUBIC_TO, 0, 1.69f, -1.38f, 3.05f, -3.08f, 3.05f,
+R_CUBIC_TO, -1.7f, 0, -3.08f, -1.37f, -3.08f, -3.05f,
+R_CUBIC_TO, 0, -1.68f, 1.38f, -3.05f, 3.08f, -3.05f,
+R_CUBIC_TO, 1.29f, 0, 2.39f, 0.78f, 2.85f, 1.89f,
+R_ARC_TO, 3.58f, 3.58f, 0, 0, 1, 2.08f, -0.01f,
+R_LINE_TO, 0, 0,
+R_ARC_TO, 3.08f, 3.08f, 0, 0, 1, 2.84f, -1.88f,
+R_CUBIC_TO, 1.7f, 0, 3.08f, 1.37f, 3.08f, 3.05f,
+CUBIC_TO_SHORTHAND, 25.54f, 27.05f, 23.84f, 27.05f,
+CLOSE,
+R_MOVE_TO, 0, -5.22f,
+R_ARC_TO, 2.18f, 2.18f, 0, 0, 0, -2.19f, 2.17f,
+R_CUBIC_TO, 0, 1.2f, 0.98f, 2.17f, 2.19f, 2.17f,
+R_ARC_TO, 2.18f, 2.18f, 0, 0, 0, 2.19f, -2.17f,
+R_ARC_TO, 2.18f, 2.18f, 0, 0, 0, -2.19f, -2.17f,
+CLOSE,
+R_MOVE_TO, -7.77f, 0,
+R_ARC_TO, 2.18f, 2.18f, 0, 0, 0, -2.19f, 2.17f,
+R_CUBIC_TO, 0, 1.2f, 0.98f, 2.17f, 2.19f, 2.17f,
+R_ARC_TO, 2.18f, 2.18f, 0, 0, 0, 2.19f, -2.17f,
+R_ARC_TO, 2.18f, 2.18f, 0, 0, 0, -2.19f, -2.17f,
+CLOSE,
+R_MOVE_TO, 3.92f, -3.98f,
+CUBIC_TO, 26.03f, 17.85f, 30, 20, 30, 20,
+H_LINE_TO, 10,
+R_CUBIC_TO, 0, 0, 3.95f, -2.15f, 9.99f, -2.15f,
+CLOSE
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 3c50c39..9f460eeb 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -127,6 +127,7 @@
 #include "services/network/public/cpp/network_switches.h"
 #include "services/resource_coordinator/public/cpp/resource_coordinator_features.h"
 #include "services/service_manager/sandbox/switches.h"
+#include "storage/browser/fileapi/file_system_features.h"
 #include "third_party/blink/public/common/experiments/memory_ablation_experiment.h"
 #include "third_party/blink/public/common/features.h"
 #include "third_party/leveldatabase/leveldb_features.h"
@@ -1707,9 +1708,6 @@
     {"gesture-typing", flag_descriptions::kGestureTypingName,
      flag_descriptions::kGestureTypingDescription, kOsCrOS,
      SINGLE_DISABLE_VALUE_TYPE(keyboard::switches::kDisableGestureTyping)},
-    {"gesture-editing", flag_descriptions::kGestureEditingName,
-     flag_descriptions::kGestureEditingDescription, kOsCrOS,
-     SINGLE_DISABLE_VALUE_TYPE(keyboard::switches::kDisableGestureEditing)},
 #endif  // OS_CHROMEOS
 #if BUILDFLAG(ENABLE_SERVICE_DISCOVERY)
     {"device-discovery-notifications",
@@ -4078,6 +4076,11 @@
      flag_descriptions::kEnableBlinkHeapUnifiedGarbageCollectionDescription,
      kOsAll, FEATURE_VALUE_TYPE(features::kBlinkHeapUnifiedGarbageCollection)},
 
+    {"enable-filesystem-in-incognito",
+     flag_descriptions::kEnableFilesystemInIncognitoName,
+     flag_descriptions::kEnableFilesystemInIncognitoDescription, kOsAll,
+     FEATURE_VALUE_TYPE(storage::features::kEnableFilesystemInIncognito)},
+
     {"enable-incognito-window-counter",
      flag_descriptions::kEnableIncognitoWindowCounterName,
      flag_descriptions::kEnableIncognitoWindowCounterDescription, kOsDesktop,
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index e8dba9c..80b45e9 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -245,6 +245,8 @@
         <include name="IDR_APP_MANAGEMENT_CHROME_APP_PERMISSION_VIEW_JS" file="resources\app_management\chrome_app_permission_view.js" type="BINDATA"/>
         <include name="IDR_APP_MANAGEMENT_CONSTANTS_HTML" file="resources\app_management\constants.html" type="BINDATA"/>
         <include name="IDR_APP_MANAGEMENT_CONSTANTS_JS" file="resources\app_management\constants.js" type="BINDATA"/>
+        <include name="IDR_APP_MANAGEMENT_EXPANDABLE_APP_LIST_HTML" file="resources\app_management\expandable_app_list.html" type="BINDATA"/>
+        <include name="IDR_APP_MANAGEMENT_EXPANDABLE_APP_LIST_JS" file="resources\app_management\expandable_app_list.js" type="BINDATA"/>
         <include name="IDR_APP_MANAGEMENT_DOM_SWITCH_HTML" file="resources\app_management\dom_switch.html" type="BINDATA"/>
         <include name="IDR_APP_MANAGEMENT_DOM_SWITCH_JS" file="resources\app_management\dom_switch.js" type="BINDATA"/>
         <include name="IDR_APP_MANAGEMENT_FAKE_PAGE_HANDLER_JS" file="resources\app_management\fake_page_handler.js" type="BINDATA" />
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 54125ae..ddb1759b 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -646,6 +646,10 @@
     "chrome_content_browser_client_chromeos_part.h",
     "chrome_service_name.cc",
     "chrome_service_name.h",
+    "crostini/crostini_export_import.cc",
+    "crostini/crostini_export_import.h",
+    "crostini/crostini_export_import_notification.cc",
+    "crostini/crostini_export_import_notification.h",
     "crostini/crostini_manager.cc",
     "crostini/crostini_manager.h",
     "crostini/crostini_manager_factory.cc",
@@ -2216,6 +2220,7 @@
     "child_accounts/time_limit_test_utils.cc",
     "child_accounts/usage_time_limit_processor_unittest.cc",
     "child_accounts/usage_time_state_notifier_unittest.cc",
+    "crostini/crostini_export_import_unittest.cc",
     "crostini/crostini_manager_unittest.cc",
     "crostini/crostini_package_service_unittest.cc",
     "crostini/crostini_share_path_unittest.cc",
diff --git a/chrome/browser/chromeos/arc/pip/arc_picture_in_picture_window_controller_impl.cc b/chrome/browser/chromeos/arc/pip/arc_picture_in_picture_window_controller_impl.cc
index abe34ec8..a48a691 100644
--- a/chrome/browser/chromeos/arc/pip/arc_picture_in_picture_window_controller_impl.cc
+++ b/chrome/browser/chromeos/arc/pip/arc_picture_in_picture_window_controller_impl.cc
@@ -99,4 +99,8 @@
   // Should be a no-op on ARC. This is managed on the Android side.
 }
 
+void ArcPictureInPictureWindowControllerImpl::PreviousTrack() {
+  // Should be a no-op on ARC. This is managed on the Android side.
+}
+
 }  // namespace arc
diff --git a/chrome/browser/chromeos/arc/pip/arc_picture_in_picture_window_controller_impl.h b/chrome/browser/chromeos/arc/pip/arc_picture_in_picture_window_controller_impl.h
index 00f6e12..2af214a 100644
--- a/chrome/browser/chromeos/arc/pip/arc_picture_in_picture_window_controller_impl.h
+++ b/chrome/browser/chromeos/arc/pip/arc_picture_in_picture_window_controller_impl.h
@@ -52,6 +52,7 @@
   void SetAlwaysHidePlayPauseButton(bool is_visible) override;
   void SkipAd() override;
   void NextTrack() override;
+  void PreviousTrack() override;
 
  private:
   arc::ArcPipBridge* const arc_pip_bridge_;
diff --git a/chrome/browser/chromeos/crostini/crostini_export_import.cc b/chrome/browser/chromeos/crostini/crostini_export_import.cc
new file mode 100644
index 0000000..51298074
--- /dev/null
+++ b/chrome/browser/chromeos/crostini/crostini_export_import.cc
@@ -0,0 +1,303 @@
+// Copyright 2019 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 "chrome/browser/chromeos/crostini/crostini_export_import.h"
+
+#include "base/bind.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "chrome/browser/chromeos/crostini/crostini_manager_factory.h"
+#include "chrome/browser/chromeos/crostini/crostini_share_path.h"
+#include "chrome/browser/chromeos/crostini/crostini_share_path_factory.h"
+#include "chrome/browser/chromeos/crostini/crostini_util.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/chrome_select_file_policy.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/web_contents.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace crostini {
+
+class CrostiniExportImportFactory : public BrowserContextKeyedServiceFactory {
+ public:
+  static CrostiniExportImport* GetForProfile(Profile* profile) {
+    return static_cast<CrostiniExportImport*>(
+        GetInstance()->GetServiceForBrowserContext(profile, true));
+  }
+
+  static CrostiniExportImportFactory* GetInstance() {
+    static base::NoDestructor<CrostiniExportImportFactory> factory;
+    return factory.get();
+  }
+
+ private:
+  friend class base::NoDestructor<CrostiniExportImportFactory>;
+
+  CrostiniExportImportFactory()
+      : BrowserContextKeyedServiceFactory(
+            "CrostiniExportImportService",
+            BrowserContextDependencyManager::GetInstance()) {
+    DependsOn(CrostiniSharePathFactory::GetInstance());
+    DependsOn(CrostiniManagerFactory::GetInstance());
+  }
+
+  ~CrostiniExportImportFactory() override = default;
+
+  // BrowserContextKeyedServiceFactory:
+  KeyedService* BuildServiceInstanceFor(
+      content::BrowserContext* context) const override {
+    Profile* profile = Profile::FromBrowserContext(context);
+    return new CrostiniExportImport(profile);
+  }
+};
+
+CrostiniExportImport* CrostiniExportImport::GetForProfile(Profile* profile) {
+  return CrostiniExportImportFactory::GetForProfile(profile);
+}
+
+CrostiniExportImport::CrostiniExportImport(Profile* profile)
+    : profile_(profile), weak_ptr_factory_(this) {
+  CrostiniManager* manager = CrostiniManager::GetForProfile(profile_);
+  manager->AddExportContainerProgressObserver(this);
+  manager->AddImportContainerProgressObserver(this);
+}
+
+CrostiniExportImport::~CrostiniExportImport() = default;
+
+void CrostiniExportImport::Shutdown() {
+  CrostiniManager* manager = CrostiniManager::GetForProfile(profile_);
+  manager->RemoveExportContainerProgressObserver(this);
+  manager->RemoveImportContainerProgressObserver(this);
+}
+
+void CrostiniExportImport::ExportContainer(content::WebContents* web_contents) {
+  if (!IsCrostiniExportImportUIAllowedForProfile(profile_)) {
+    return;
+  }
+  OpenFileDialog(ExportImportType::EXPORT, web_contents);
+}
+
+void CrostiniExportImport::ImportContainer(content::WebContents* web_contents) {
+  if (!IsCrostiniExportImportUIAllowedForProfile(profile_)) {
+    return;
+  }
+  OpenFileDialog(ExportImportType::IMPORT, web_contents);
+}
+
+void CrostiniExportImport::OpenFileDialog(ExportImportType type,
+                                          content::WebContents* web_contents) {
+  PrefService* pref_service = profile_->GetPrefs();
+  ui::SelectFileDialog::Type file_selector_mode;
+  unsigned title = 0;
+  base::FilePath default_path;
+  ui::SelectFileDialog::FileTypeInfo file_types;
+  file_types.allowed_paths =
+      ui::SelectFileDialog::FileTypeInfo::NATIVE_OR_DRIVE_PATH;
+  file_types.extensions = {{"tar.gz", "tgz"}};
+
+  switch (type) {
+    case ExportImportType::EXPORT:
+      file_selector_mode = ui::SelectFileDialog::SELECT_SAVEAS_FILE;
+      title = IDS_CROSTINI_EXPORT_TITLE;
+      base::Time::Exploded exploded;
+      base::Time::Now().LocalExplode(&exploded);
+      default_path = pref_service->GetFilePath(prefs::kDownloadDefaultDirectory)
+                         .Append(base::StringPrintf(
+                             "crostini-%04d-%02d-%02d.tar.gz", exploded.year,
+                             exploded.month, exploded.day_of_month));
+      break;
+    case ExportImportType::IMPORT:
+      file_selector_mode = ui::SelectFileDialog::SELECT_OPEN_FILE,
+      title = IDS_CROSTINI_IMPORT_TITLE;
+      default_path =
+          pref_service->GetFilePath(prefs::kDownloadDefaultDirectory);
+      break;
+  }
+
+  select_folder_dialog_ = ui::SelectFileDialog::Create(
+      this, std::make_unique<ChromeSelectFilePolicy>(web_contents));
+  select_folder_dialog_->SelectFile(
+      file_selector_mode, l10n_util::GetStringUTF16(title), default_path,
+      &file_types, 0, base::FilePath::StringType(),
+      web_contents->GetTopLevelNativeWindow(), reinterpret_cast<void*>(type));
+}
+
+void CrostiniExportImport::FileSelected(const base::FilePath& path,
+                                        int index,
+                                        void* params) {
+  ExportImportType type =
+      static_cast<ExportImportType>(reinterpret_cast<uintptr_t>(params));
+  ContainerId container_id(crostini::kCrostiniDefaultVmName,
+                           crostini::kCrostiniDefaultContainerName);
+  notifications_[container_id] =
+      std::make_unique<CrostiniExportImportNotification>(
+          profile_, type, GetUniqueNotificationId(), path);
+
+  switch (type) {
+    case ExportImportType::EXPORT:
+      crostini::CrostiniSharePath::GetForProfile(profile_)->SharePath(
+          crostini::kCrostiniDefaultVmName, path.DirName(), false,
+          base::BindOnce(&CrostiniExportImport::ExportAfterSharing,
+                         weak_ptr_factory_.GetWeakPtr(), container_id,
+                         path.BaseName()));
+      break;
+    case ExportImportType::IMPORT:
+      crostini::CrostiniSharePath::GetForProfile(profile_)->SharePath(
+          crostini::kCrostiniDefaultVmName, path, false,
+          base::BindOnce(&CrostiniExportImport::ImportAfterSharing,
+                         weak_ptr_factory_.GetWeakPtr(), container_id));
+      break;
+  }
+}
+
+void CrostiniExportImport::ExportAfterSharing(
+    const ContainerId& container_id,
+    const base::FilePath& filename,
+    const base::FilePath& container_path,
+    bool result,
+    const std::string failure_reason) {
+  if (!result) {
+    LOG(ERROR) << "Error sharing for export " << container_path.value() << ": "
+               << failure_reason;
+    auto it = notifications_.find(container_id);
+    DCHECK(it != notifications_.end()) << ContainerIdToString(container_id)
+                                       << " has no notification to update";
+    it->second->UpdateStatus(CrostiniExportImportNotification::Status::FAILED,
+                             0);
+    return;
+  }
+  CrostiniManager::GetForProfile(profile_)->ExportLxdContainer(
+      crostini::kCrostiniDefaultVmName, crostini::kCrostiniDefaultContainerName,
+      container_path.Append(filename),
+      base::BindOnce(&CrostiniExportImport::OnExportComplete,
+                     weak_ptr_factory_.GetWeakPtr(), container_id));
+}
+
+void CrostiniExportImport::OnExportComplete(const ContainerId& container_id,
+                                            CrostiniResult result) {
+  auto it = notifications_.find(container_id);
+  DCHECK(it != notifications_.end())
+      << ContainerIdToString(container_id) << " has no notification to update";
+
+  if (result != crostini::CrostiniResult::SUCCESS) {
+    LOG(ERROR) << "Error exporting " << int(result);
+    it->second->UpdateStatus(CrostiniExportImportNotification::Status::FAILED,
+                             0);
+  } else {
+    it->second->UpdateStatus(CrostiniExportImportNotification::Status::DONE, 0);
+  }
+  notifications_.erase(it);
+}
+
+void CrostiniExportImport::OnExportContainerProgress(
+    const std::string& vm_name,
+    const std::string& container_name,
+    crostini::ExportContainerProgressStatus status,
+    int progress_percent,
+    uint64_t progress_speed) {
+  ContainerId container_id(vm_name, container_name);
+  auto it = notifications_.find(container_id);
+  DCHECK(it != notifications_.end())
+      << ContainerIdToString(container_id) << " has no notification to update";
+
+  switch (status) {
+    // Rescale PACK:1-100 => 0-50.
+    case crostini::ExportContainerProgressStatus::PACK:
+      it->second->UpdateStatus(
+          CrostiniExportImportNotification::Status::RUNNING,
+          progress_percent / 2);
+      break;
+    // Rescale DOWNLOAD:1-100 => 50-100.
+    case crostini::ExportContainerProgressStatus::DOWNLOAD:
+      it->second->UpdateStatus(
+          CrostiniExportImportNotification::Status::RUNNING,
+          50 + progress_percent / 2);
+      break;
+    default:
+      LOG(WARNING) << "Unknown Export progress status " << int(status);
+  }
+}
+
+void CrostiniExportImport::ImportAfterSharing(
+    const ContainerId& container_id,
+    const base::FilePath& container_path,
+    bool result,
+    const std::string failure_reason) {
+  if (!result) {
+    LOG(ERROR) << "Error sharing for import " << container_path.value() << ": "
+               << failure_reason;
+    auto it = notifications_.find(container_id);
+    DCHECK(it != notifications_.end()) << ContainerIdToString(container_id)
+                                       << " has no notification to update";
+    it->second->UpdateStatus(CrostiniExportImportNotification::Status::FAILED,
+                             0);
+    return;
+  }
+  CrostiniManager::GetForProfile(profile_)->ImportLxdContainer(
+      crostini::kCrostiniDefaultVmName, crostini::kCrostiniDefaultContainerName,
+      container_path,
+      base::BindOnce(&CrostiniExportImport::OnImportComplete,
+                     weak_ptr_factory_.GetWeakPtr(), container_id));
+}
+
+void CrostiniExportImport::OnImportComplete(const ContainerId& container_id,
+                                            CrostiniResult result) {
+  auto it = notifications_.find(container_id);
+  DCHECK(it != notifications_.end())
+      << ContainerIdToString(container_id) << " has no notification to update";
+  if (result != crostini::CrostiniResult::SUCCESS) {
+    LOG(ERROR) << "Error importing " << int(result);
+    it->second->UpdateStatus(CrostiniExportImportNotification::Status::FAILED,
+                             0);
+  } else {
+    it->second->UpdateStatus(CrostiniExportImportNotification::Status::DONE, 0);
+  }
+  notifications_.erase(it);
+}
+
+void CrostiniExportImport::OnImportContainerProgress(
+    const std::string& vm_name,
+    const std::string& container_name,
+    crostini::ImportContainerProgressStatus status,
+    int progress_percent,
+    uint64_t progress_speed) {
+  ContainerId container_id(vm_name, container_name);
+  auto it = notifications_.find(container_id);
+  DCHECK(it != notifications_.end())
+      << ContainerIdToString(container_id) << " has no notification to update";
+
+  switch (status) {
+    // Rescale UPLOAD:1-100 => 0-50.
+    case crostini::ImportContainerProgressStatus::UPLOAD:
+      it->second->UpdateStatus(
+          CrostiniExportImportNotification::Status::RUNNING,
+          progress_percent / 2);
+      break;
+    // Rescale UNPACK:1-100 => 50-100.
+    case crostini::ImportContainerProgressStatus::UNPACK:
+      it->second->UpdateStatus(
+          CrostiniExportImportNotification::Status::RUNNING,
+          50 + progress_percent / 2);
+      break;
+    default:
+      LOG(WARNING) << "Unknown Export progress status " << int(status);
+  }
+}
+
+std::string CrostiniExportImport::GetUniqueNotificationId() {
+  return base::StringPrintf("crostini_export_import_%d",
+                            next_notification_id_++);
+}
+
+CrostiniExportImportNotification*
+CrostiniExportImport::GetNotificationForTesting(ContainerId container_id) {
+  auto it = notifications_.find(container_id);
+  return it != notifications_.end() ? it->second.get() : nullptr;
+}
+
+}  // namespace crostini
diff --git a/chrome/browser/chromeos/crostini/crostini_export_import.h b/chrome/browser/chromeos/crostini/crostini_export_import.h
new file mode 100644
index 0000000..12f792e
--- /dev/null
+++ b/chrome/browser/chromeos/crostini/crostini_export_import.h
@@ -0,0 +1,114 @@
+// Copyright 2019 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 CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_EXPORT_IMPORT_H_
+#define CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_EXPORT_IMPORT_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/chromeos/crostini/crostini_export_import_notification.h"
+#include "chrome/browser/chromeos/crostini/crostini_manager.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+class Profile;
+
+namespace content {
+class WebContents;
+}
+
+namespace crostini {
+
+enum class CrostiniResult;
+
+enum class ExportImportType { EXPORT, IMPORT };
+
+// CrostiniExportImport is a keyed profile service to manage exporting and
+// importing containers with crostini.  It manages a file dialog for selecting
+// files and a notification to show the progress of export/import.
+//
+// TODO(crbug.com/932339): Ensure we have enough free space before doing
+// backup or restore.
+class CrostiniExportImport : public KeyedService,
+                             public ui::SelectFileDialog::Listener,
+                             public crostini::ExportContainerProgressObserver,
+                             public crostini::ImportContainerProgressObserver {
+ public:
+  static CrostiniExportImport* GetForProfile(Profile* profile);
+
+  explicit CrostiniExportImport(Profile* profile);
+  ~CrostiniExportImport() override;
+
+  // KeyedService:
+  void Shutdown() override;
+
+  // Export the crostini container.
+  void ExportContainer(content::WebContents* web_contents);
+  // Import the crostini container.
+  void ImportContainer(content::WebContents* web_contents);
+
+  CrostiniExportImportNotification* GetNotificationForTesting(
+      ContainerId container_id);
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(CrostiniExportImportTest, TestExportSuccess);
+  FRIEND_TEST_ALL_PREFIXES(CrostiniExportImportTest, TestExportFail);
+  FRIEND_TEST_ALL_PREFIXES(CrostiniExportImportTest, TestImportSuccess);
+  FRIEND_TEST_ALL_PREFIXES(CrostiniExportImportTest, TestImportFail);
+
+  // ui::SelectFileDialog::Listener implementation.
+  void FileSelected(const base::FilePath& path,
+                    int index,
+                    void* params) override;
+
+  // crostini::ExportContainerProgressObserver implementation.
+  void OnExportContainerProgress(const std::string& vm_name,
+                                 const std::string& container_name,
+                                 crostini::ExportContainerProgressStatus status,
+                                 int progress_percent,
+                                 uint64_t progress_speed) override;
+
+  // crostini::ImportContainerProgressObserver implementation.
+  void OnImportContainerProgress(const std::string& vm_name,
+                                 const std::string& container_name,
+                                 crostini::ImportContainerProgressStatus status,
+                                 int progress_percent,
+                                 uint64_t progress_speed) override;
+
+  void ExportAfterSharing(const ContainerId& container_id,
+                          const base::FilePath& filename,
+                          const base::FilePath& container_path,
+                          bool result,
+                          const std::string failure_reason);
+  void OnExportComplete(const ContainerId& container_id, CrostiniResult result);
+
+  void ImportAfterSharing(const ContainerId& container_id,
+                          const base::FilePath& container_path,
+                          bool result,
+                          const std::string failure_reason);
+  void OnImportComplete(const ContainerId& container_id, CrostiniResult result);
+
+  void OpenFileDialog(ExportImportType type,
+                      content::WebContents* web_contents);
+
+  std::string GetUniqueNotificationId();
+
+  Profile* profile_;
+  scoped_refptr<ui::SelectFileDialog> select_folder_dialog_;
+  std::map<ContainerId, std::unique_ptr<CrostiniExportImportNotification>>
+      notifications_;
+  // Notifications must have unique-per-profile identifiers.
+  // A non-static member on a profile-keyed-service will suffice.
+  int next_notification_id_;
+  // weak_ptr_factory_ should always be last member.
+  base::WeakPtrFactory<CrostiniExportImport> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(CrostiniExportImport);
+};
+
+}  // namespace crostini
+
+#endif  // CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_EXPORT_IMPORT_H_
diff --git a/chrome/browser/chromeos/crostini/crostini_export_import_notification.cc b/chrome/browser/chromeos/crostini/crostini_export_import_notification.cc
new file mode 100644
index 0000000..6af4316
--- /dev/null
+++ b/chrome/browser/chromeos/crostini/crostini_export_import_notification.cc
@@ -0,0 +1,119 @@
+// Copyright 2019 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 "chrome/browser/chromeos/crostini/crostini_export_import_notification.h"
+
+#include "ash/public/cpp/notification_utils.h"
+#include "ash/public/cpp/vector_icons/vector_icons.h"
+#include "chrome/browser/chromeos/crostini/crostini_export_import.h"
+#include "chrome/browser/notifications/notification_display_service.h"
+#include "chrome/browser/platform_util_chromeos.cc"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/message_center/public/cpp/message_center_constants.h"
+#include "ui/message_center/public/cpp/notification.h"
+#include "ui/message_center/public/cpp/notification_delegate.h"
+
+namespace crostini {
+
+namespace {
+
+constexpr char kNotifierCrostiniExportImportOperation[] =
+    "crostini.export_operation";
+
+}  // namespace
+
+CrostiniExportImportNotification::CrostiniExportImportNotification(
+    Profile* profile,
+    ExportImportType type,
+    const std::string& notification_id,
+    const base::FilePath& path)
+    : profile_(profile), type_(type), path_(path), weak_ptr_factory_(this) {
+  // Messages.
+  switch (type) {
+    case ExportImportType::EXPORT:
+      message_title_ = IDS_CROSTINI_EXPORT_TITLE;
+      message_running_ = IDS_CROSTINI_EXPORT_NOTIFICATION_IN_PROGRESS;
+      message_done_ = IDS_CROSTINI_EXPORT_NOTIFICATION_DONE;
+      message_failed_ = IDS_CROSTINI_EXPORT_NOTIFICATION_FAILED;
+      break;
+    case ExportImportType::IMPORT:
+      message_title_ = IDS_CROSTINI_IMPORT_TITLE;
+      message_running_ = IDS_CROSTINI_IMPORT_NOTIFICATION_IN_PROGRESS;
+      message_done_ = IDS_CROSTINI_IMPORT_NOTIFICATION_DONE;
+      message_failed_ = IDS_CROSTINI_IMPORT_NOTIFICATION_FAILED;
+      break;
+    default:
+      NOTREACHED();
+  }
+
+  message_center::RichNotificationData rich_notification_data;
+  rich_notification_data.vector_small_image = &ash::kNotificationLinuxIcon;
+  rich_notification_data.accent_color = ash::kSystemNotificationColorNormal;
+
+  notification_ = std::make_unique<message_center::Notification>(
+      message_center::NOTIFICATION_TYPE_PROGRESS, notification_id,
+      base::string16(), base::string16(),
+      gfx::Image(),  // icon
+      l10n_util::GetStringUTF16(message_title_),
+      GURL(),  // origin_url
+      message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
+                                 kNotifierCrostiniExportImportOperation),
+      rich_notification_data,
+      base::MakeRefCounted<message_center::ThunkNotificationDelegate>(
+          weak_ptr_factory_.GetWeakPtr()));
+
+  UpdateStatus(Status::RUNNING, 0);
+}
+
+CrostiniExportImportNotification::~CrostiniExportImportNotification() = default;
+
+void CrostiniExportImportNotification::UpdateStatus(Status status,
+                                                    int progress_percent) {
+  status_ = status;
+  switch (status) {
+    case Status::RUNNING:
+      if (closed_) {
+        return;
+      }
+      notification_->set_type(message_center::NOTIFICATION_TYPE_PROGRESS);
+      notification_->set_progress(progress_percent);
+      notification_->set_message(l10n_util::GetStringUTF16(message_running_));
+      notification_->set_never_timeout(true);
+      break;
+    case Status::DONE:
+      notification_->set_type(message_center::NOTIFICATION_TYPE_SIMPLE);
+      notification_->set_message(l10n_util::GetStringUTF16(message_done_));
+      notification_->set_never_timeout(false);
+      break;
+    case Status::FAILED:
+      notification_->set_type(message_center::NOTIFICATION_TYPE_SIMPLE);
+      notification_->set_accent_color(
+          ash::kSystemNotificationColorCriticalWarning);
+      notification_->set_message(l10n_util::GetStringUTF16(message_failed_));
+      notification_->set_never_timeout(false);
+      break;
+    default:
+      NOTREACHED();
+  }
+  NotificationDisplayService* display_service =
+      NotificationDisplayService::GetForProfile(profile_);
+  display_service->Display(NotificationHandler::Type::TRANSIENT,
+                           *notification_);
+}
+
+void CrostiniExportImportNotification::Close(bool by_user) {
+  closed_ = true;
+}
+
+void CrostiniExportImportNotification::Click(
+    const base::Optional<int>& button_index,
+    const base::Optional<base::string16>& reply) {
+  if (type_ == ExportImportType::EXPORT) {
+    platform_util::ShowItemInFolder(profile_, path_);
+  }
+}
+
+}  // namespace crostini
diff --git a/chrome/browser/chromeos/crostini/crostini_export_import_notification.h b/chrome/browser/chromeos/crostini/crostini_export_import_notification.h
new file mode 100644
index 0000000..a7f90f0
--- /dev/null
+++ b/chrome/browser/chromeos/crostini/crostini_export_import_notification.h
@@ -0,0 +1,68 @@
+// Copyright 2019 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 CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_EXPORT_IMPORT_NOTIFICATION_H_
+#define CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_EXPORT_IMPORT_NOTIFICATION_H_
+
+#include <memory>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "ui/message_center/public/cpp/notification_delegate.h"
+
+class Profile;
+
+namespace message_center {
+class Notification;
+}
+
+namespace crostini {
+
+enum class ExportImportType;
+
+// Notification for Crostini export and import.
+class CrostiniExportImportNotification
+    : public message_center::NotificationObserver {
+ public:
+  enum class Status { RUNNING, DONE, FAILED };
+
+  CrostiniExportImportNotification(Profile* profile,
+                                   ExportImportType type,
+                                   const std::string& notification_id,
+                                   const base::FilePath& path);
+  virtual ~CrostiniExportImportNotification();
+
+  void UpdateStatus(Status status, int progress_percent);
+
+  // Getters for testing.
+  Status get_status() { return status_; }
+  message_center::Notification* get_notification() {
+    return notification_.get();
+  }
+
+  // message_center::NotificationObserver:
+  void Close(bool by_user) override;
+  void Click(const base::Optional<int>& button_index,
+             const base::Optional<base::string16>& reply) override;
+
+ private:
+  Profile* profile_;
+  ExportImportType type_;
+  base::FilePath path_;
+  // These notifications are owned by the export service.
+  Status status_ = Status::RUNNING;
+  int message_title_;
+  int message_running_;
+  int message_done_;
+  int message_failed_;
+  std::unique_ptr<message_center::Notification> notification_;
+  bool closed_ = false;
+  base::WeakPtrFactory<CrostiniExportImportNotification> weak_ptr_factory_;
+  DISALLOW_COPY_AND_ASSIGN(CrostiniExportImportNotification);
+};
+
+}  // namespace crostini
+
+#endif  // CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_EXPORT_IMPORT_NOTIFICATION_H_
diff --git a/chrome/browser/chromeos/crostini/crostini_export_import_unittest.cc b/chrome/browser/chromeos/crostini/crostini_export_import_unittest.cc
new file mode 100644
index 0000000..28de2c3
--- /dev/null
+++ b/chrome/browser/chromeos/crostini/crostini_export_import_unittest.cc
@@ -0,0 +1,227 @@
+// Copyright 2019 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 "chrome/browser/chromeos/crostini/crostini_export_import.h"
+
+#include "base/files/file_path.h"
+#include "base/run_loop.h"
+#include "chrome/browser/chromeos/crostini/crostini_util.h"
+#include "chrome/browser/chromeos/file_manager/path_util.h"
+#include "chrome/test/base/testing_profile.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/fake_cicerone_client.h"
+#include "chromeos/dbus/fake_seneschal_client.h"
+#include "chromeos/dbus/seneschal/seneschal_service.pb.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "storage/browser/fileapi/external_mount_points.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/message_center/public/cpp/notification.h"
+
+namespace crostini {
+
+class CrostiniExportImportTest : public testing::Test {
+ public:
+  void SendExportProgress(
+      vm_tools::cicerone::ExportLxdContainerProgressSignal_Status status,
+      int progress_percent) {
+    vm_tools::cicerone::ExportLxdContainerProgressSignal signal;
+    signal.set_owner_id(CryptohomeIdForProfile(profile()));
+    signal.set_vm_name(kCrostiniDefaultVmName);
+    signal.set_container_name(kCrostiniDefaultContainerName);
+    signal.set_status(status);
+    signal.set_progress_percent(progress_percent);
+    fake_cicerone_client_->NotifyExportLxdContainerProgress(signal);
+  }
+
+  void SendImportProgress(
+      vm_tools::cicerone::ImportLxdContainerProgressSignal_Status status,
+      int progress_percent) {
+    vm_tools::cicerone::ImportLxdContainerProgressSignal signal;
+    signal.set_owner_id(CryptohomeIdForProfile(profile()));
+    signal.set_vm_name(kCrostiniDefaultVmName);
+    signal.set_container_name(kCrostiniDefaultContainerName);
+    signal.set_status(status);
+    signal.set_progress_percent(progress_percent);
+    fake_cicerone_client_->NotifyImportLxdContainerProgress(signal);
+  }
+
+  CrostiniExportImportTest()
+      : test_browser_thread_bundle_(
+            content::TestBrowserThreadBundle::REAL_IO_THREAD) {
+    chromeos::DBusThreadManager::Initialize();
+    fake_seneschal_client_ = static_cast<chromeos::FakeSeneschalClient*>(
+        chromeos::DBusThreadManager::Get()->GetSeneschalClient());
+    fake_cicerone_client_ = static_cast<chromeos::FakeCiceroneClient*>(
+        chromeos::DBusThreadManager::Get()->GetCiceroneClient());
+  }
+
+  ~CrostiniExportImportTest() override {
+    chromeos::DBusThreadManager::Shutdown();
+  }
+
+  void SetUp() override {
+    run_loop_ = std::make_unique<base::RunLoop>();
+    profile_ = std::make_unique<TestingProfile>();
+    crostini_export_import_ = std::make_unique<CrostiniExportImport>(profile());
+    CrostiniManager::GetForProfile(profile())->AddRunningVmForTesting(
+        kCrostiniDefaultVmName);
+    container_id_ =
+        ContainerId(kCrostiniDefaultVmName, kCrostiniDefaultContainerName);
+
+    storage::ExternalMountPoints* mount_points =
+        storage::ExternalMountPoints::GetSystemInstance();
+    mount_points->RegisterFileSystem(
+        file_manager::util::GetDownloadsMountPointName(profile()),
+        storage::kFileSystemTypeNativeLocal, storage::FileSystemMountOption(),
+        file_manager::util::GetMyFilesFolderForProfile(profile()));
+    tarball_ = file_manager::util::GetMyFilesFolderForProfile(profile()).Append(
+        "tarball.tar.gz");
+  }
+
+  void TearDown() override {
+    run_loop_.reset();
+    profile_.reset();
+  }
+
+ protected:
+  base::RunLoop* run_loop() { return run_loop_.get(); }
+  Profile* profile() { return profile_.get(); }
+  CrostiniExportImport* crostini_export_import() {
+    return crostini_export_import_.get();
+  }
+
+  // Owned by chromeos::DBusThreadManager
+  chromeos::FakeCiceroneClient* fake_cicerone_client_;
+  chromeos::FakeSeneschalClient* fake_seneschal_client_;
+
+  std::unique_ptr<base::RunLoop> run_loop_;
+  std::unique_ptr<TestingProfile> profile_;
+  std::unique_ptr<CrostiniExportImport> crostini_export_import_;
+  ContainerId container_id_;
+  base::FilePath tarball_;
+
+ private:
+  content::TestBrowserThreadBundle test_browser_thread_bundle_;
+  DISALLOW_COPY_AND_ASSIGN(CrostiniExportImportTest);
+};
+
+TEST_F(CrostiniExportImportTest, DISABLED_TestExportSuccess) {
+  crostini_export_import()->FileSelected(
+      tarball_, 0, reinterpret_cast<void*>(ExportImportType::EXPORT));
+  run_loop_->RunUntilIdle();
+  CrostiniExportImportNotification* notification =
+      crostini_export_import()->GetNotificationForTesting(container_id_);
+  EXPECT_NE(notification, nullptr);
+  EXPECT_EQ(notification->get_status(),
+            CrostiniExportImportNotification::Status::RUNNING);
+  EXPECT_EQ(notification->get_notification()->progress(), 0);
+
+  // 20% PACK = 10% overall.
+  SendExportProgress(vm_tools::cicerone::
+                         ExportLxdContainerProgressSignal_Status_EXPORTING_PACK,
+                     20);
+  EXPECT_EQ(notification->get_status(),
+            CrostiniExportImportNotification::Status::RUNNING);
+  EXPECT_EQ(notification->get_notification()->progress(), 10);
+
+  // 20% DOWNLOAD = 60% overall.
+  SendExportProgress(
+      vm_tools::cicerone::
+          ExportLxdContainerProgressSignal_Status_EXPORTING_DOWNLOAD,
+      20);
+  EXPECT_EQ(notification->get_status(),
+            CrostiniExportImportNotification::Status::RUNNING);
+  EXPECT_EQ(notification->get_notification()->progress(), 60);
+
+  // Close notification and update progress.  Should not update notification.
+  notification->Close(false);
+  SendExportProgress(
+      vm_tools::cicerone::
+          ExportLxdContainerProgressSignal_Status_EXPORTING_DOWNLOAD,
+      40);
+  EXPECT_EQ(notification->get_status(),
+            CrostiniExportImportNotification::Status::RUNNING);
+  EXPECT_EQ(notification->get_notification()->progress(), 60);
+
+  // Done.
+  SendExportProgress(
+      vm_tools::cicerone::ExportLxdContainerProgressSignal_Status_DONE, 0);
+  EXPECT_EQ(notification->get_status(),
+            CrostiniExportImportNotification::Status::DONE);
+}
+
+TEST_F(CrostiniExportImportTest, DISABLED_TestExportFail) {
+  crostini_export_import()->FileSelected(
+      tarball_, 0, reinterpret_cast<void*>(ExportImportType::EXPORT));
+  run_loop_->RunUntilIdle();
+  CrostiniExportImportNotification* notification =
+      crostini_export_import()->GetNotificationForTesting(container_id_);
+
+  // Failed.
+  SendExportProgress(
+      vm_tools::cicerone::ExportLxdContainerProgressSignal_Status_FAILED, 0);
+  EXPECT_EQ(notification->get_status(),
+            CrostiniExportImportNotification::Status::FAILED);
+}
+
+TEST_F(CrostiniExportImportTest, DISABLED_TestImportSuccess) {
+  crostini_export_import()->FileSelected(
+      tarball_, 0, reinterpret_cast<void*>(ExportImportType::IMPORT));
+  run_loop_->RunUntilIdle();
+  CrostiniExportImportNotification* notification =
+      crostini_export_import()->GetNotificationForTesting(container_id_);
+  EXPECT_NE(notification, nullptr);
+  EXPECT_EQ(notification->get_status(),
+            CrostiniExportImportNotification::Status::RUNNING);
+  EXPECT_EQ(notification->get_notification()->progress(), 0);
+
+  // 20% UPLOAD = 10% overall.
+  SendImportProgress(
+      vm_tools::cicerone::
+          ImportLxdContainerProgressSignal_Status_IMPORTING_UPLOAD,
+      20);
+  EXPECT_EQ(notification->get_status(),
+            CrostiniExportImportNotification::Status::RUNNING);
+  EXPECT_EQ(notification->get_notification()->progress(), 10);
+
+  // 20% UNPACK = 60% overall.
+  SendImportProgress(
+      vm_tools::cicerone::
+          ImportLxdContainerProgressSignal_Status_IMPORTING_UNPACK,
+      20);
+  EXPECT_EQ(notification->get_status(),
+            CrostiniExportImportNotification::Status::RUNNING);
+  EXPECT_EQ(notification->get_notification()->progress(), 60);
+
+  // Close notification and update progress.  Should not update notification.
+  notification->Close(false);
+  SendImportProgress(
+      vm_tools::cicerone::
+          ImportLxdContainerProgressSignal_Status_IMPORTING_UNPACK,
+      40);
+  EXPECT_EQ(notification->get_status(),
+            CrostiniExportImportNotification::Status::RUNNING);
+  EXPECT_EQ(notification->get_notification()->progress(), 60);
+
+  // Done.
+  SendImportProgress(
+      vm_tools::cicerone::ImportLxdContainerProgressSignal_Status_DONE, 0);
+  EXPECT_EQ(notification->get_status(),
+            CrostiniExportImportNotification::Status::DONE);
+}
+
+TEST_F(CrostiniExportImportTest, DISABLED_TestImportFail) {
+  crostini_export_import()->FileSelected(
+      tarball_, 0, reinterpret_cast<void*>(ExportImportType::IMPORT));
+  run_loop_->RunUntilIdle();
+  CrostiniExportImportNotification* notification =
+      crostini_export_import()->GetNotificationForTesting(container_id_);
+
+  // Failed.
+  SendImportProgress(
+      vm_tools::cicerone::ImportLxdContainerProgressSignal_Status_FAILED, 0);
+  EXPECT_EQ(notification->get_status(),
+            CrostiniExportImportNotification::Status::FAILED);
+}
+}  // namespace crostini
diff --git a/chrome/browser/chromeos/crostini/crostini_manager.cc b/chrome/browser/chromeos/crostini/crostini_manager.cc
index 64dabaa5..687bac8 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager.cc
+++ b/chrome/browser/chromeos/crostini/crostini_manager.cc
@@ -1106,7 +1106,7 @@
     return;
   }
 
-  auto key = std::make_pair(vm_name, container_name);
+  ContainerId key(vm_name, container_name);
   if (export_lxd_container_callbacks_.find(key) !=
       export_lxd_container_callbacks_.end()) {
     LOG(ERROR) << "Export currently in progress for " << vm_name << ", "
@@ -1147,7 +1147,7 @@
     std::move(callback).Run(CrostiniResult::CLIENT_ERROR);
     return;
   }
-  auto key = std::make_pair(vm_name, container_name);
+  ContainerId key(vm_name, container_name);
   if (import_lxd_container_callbacks_.find(key) !=
       import_lxd_container_callbacks_.end()) {
     LOG(ERROR) << "Import currently in progress for " << vm_name << ", "
@@ -1745,8 +1745,8 @@
     std::string vm_name,
     std::string container_name,
     ShutdownContainerCallback shutdown_callback) {
-  shutdown_container_callbacks_.emplace(
-      std::make_tuple(vm_name, container_name), std::move(shutdown_callback));
+  shutdown_container_callbacks_.emplace(ContainerId(vm_name, container_name),
+                                        std::move(shutdown_callback));
 }
 
 void CrostiniManager::AddRemoveCrostiniCallback(
@@ -2129,7 +2129,7 @@
     // The callback will be called when we receive the LxdContainerCreated
     // signal.
     create_lxd_container_callbacks_.emplace(
-        std::make_tuple(vm_name, container_name), std::move(callback));
+        ContainerId(vm_name, container_name), std::move(callback));
     return;
   }
   if (response.status() !=
@@ -2179,7 +2179,7 @@
       // The callback will be called when we receive the LxdContainerStarting
       // signal.
       start_lxd_container_callbacks_.emplace(
-          std::make_tuple(vm_name, container_name), std::move(callback));
+          ContainerId(vm_name, container_name), std::move(callback));
       break;
     default:
       NOTREACHED();
@@ -2210,7 +2210,7 @@
   }
 
   if (!GetContainerInfo(vm_name, container_name)) {
-    start_container_callbacks_.emplace(std::make_tuple(vm_name, container_name),
+    start_container_callbacks_.emplace(ContainerId(vm_name, container_name),
                                        std::move(callback));
     return;
   }
@@ -2535,7 +2535,7 @@
     std::string vm_name,
     std::string container_name,
     base::Optional<vm_tools::cicerone::ExportLxdContainerResponse> reply) {
-  auto key = std::make_pair(vm_name, container_name);
+  ContainerId key(vm_name, container_name);
   auto it = export_lxd_container_callbacks_.find(key);
   if (it == export_lxd_container_callbacks_.end()) {
     LOG(ERROR) << "No export callback for " << vm_name << ", "
@@ -2571,14 +2571,9 @@
   ExportContainerProgressStatus status;
   CrostiniResult result;
   switch (signal.status()) {
-    case vm_tools::cicerone::ExportLxdContainerProgressSignal::EXPORTING_TAR:
+    case vm_tools::cicerone::ExportLxdContainerProgressSignal::EXPORTING_PACK:
       exporting = true;
-      status = ExportContainerProgressStatus::TAR;
-      break;
-    case vm_tools::cicerone::ExportLxdContainerProgressSignal::
-        EXPORTING_COMPRESS:
-      exporting = true;
-      status = ExportContainerProgressStatus::COMPRESS;
+      status = ExportContainerProgressStatus::PACK;
       break;
     case vm_tools::cicerone::ExportLxdContainerProgressSignal::
         EXPORTING_DOWNLOAD:
@@ -2620,7 +2615,7 @@
     std::string vm_name,
     std::string container_name,
     base::Optional<vm_tools::cicerone::ImportLxdContainerResponse> reply) {
-  auto key = std::make_pair(vm_name, container_name);
+  ContainerId key(vm_name, container_name);
   auto it = import_lxd_container_callbacks_.find(key);
   if (it == import_lxd_container_callbacks_.end()) {
     LOG(ERROR) << "No import callback for " << vm_name << ", "
diff --git a/chrome/browser/chromeos/crostini/crostini_manager.h b/chrome/browser/chromeos/crostini/crostini_manager.h
index 4b9f79a2..d319fca 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager.h
+++ b/chrome/browser/chromeos/crostini/crostini_manager.h
@@ -90,8 +90,7 @@
 };
 
 enum class ExportContainerProgressStatus {
-  TAR,
-  COMPRESS,
+  PACK,
   DOWNLOAD,
 };
 
diff --git a/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc b/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc
index 0b9ec266..bc0245c 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc
+++ b/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc
@@ -1120,18 +1120,13 @@
                      base::Unretained(this), run_loop()->QuitClosure(),
                      CrostiniResult::SUCCESS));
 
-  // Send signals, TAR, COMPRESS, DOWNLOAD, DONE.
+  // Send signals, PACK, DOWNLOAD, DONE.
   vm_tools::cicerone::ExportLxdContainerProgressSignal signal;
   signal.set_owner_id(CryptohomeIdForProfile(profile()));
   signal.set_vm_name(kVmName);
   signal.set_container_name(kContainerName);
   signal.set_status(vm_tools::cicerone::
-                        ExportLxdContainerProgressSignal_Status_EXPORTING_TAR);
-  fake_cicerone_client_->NotifyExportLxdContainerProgress(signal);
-
-  signal.set_status(
-      vm_tools::cicerone::
-          ExportLxdContainerProgressSignal_Status_EXPORTING_COMPRESS);
+                        ExportLxdContainerProgressSignal_Status_EXPORTING_PACK);
   fake_cicerone_client_->NotifyExportLxdContainerProgress(signal);
 
   signal.set_status(
diff --git a/chrome/browser/chromeos/crostini/crostini_util.cc b/chrome/browser/chromeos/crostini/crostini_util.cc
index 0c09058..e40765a 100644
--- a/chrome/browser/chromeos/crostini/crostini_util.cc
+++ b/chrome/browser/chromeos/crostini/crostini_util.cc
@@ -35,6 +35,7 @@
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/grit/chrome_unscaled_resources.h"
+#include "chromeos/constants/chromeos_features.h"
 #include "components/prefs/pref_service.h"
 #include "google_apis/gaia/gaia_auth_util.h"
 
@@ -260,6 +261,13 @@
   return IsCrostiniAllowedForProfileImpl(profile);
 }
 
+bool IsCrostiniExportImportUIAllowedForProfile(Profile* profile) {
+  return IsCrostiniUIAllowedForProfile(profile, true) &&
+         base::FeatureList::IsEnabled(chromeos::features::kCrostiniBackup) &&
+         profile->GetPrefs()->GetBoolean(
+             crostini::prefs::kUserCrostiniExportImportUIAllowedByPolicy);
+}
+
 bool IsCrostiniEnabled(Profile* profile) {
   return IsCrostiniUIAllowedForProfile(profile) &&
          profile->GetPrefs()->GetBoolean(crostini::prefs::kCrostiniEnabled);
diff --git a/chrome/browser/chromeos/crostini/crostini_util.h b/chrome/browser/chromeos/crostini/crostini_util.h
index 5365ac4..343540e 100644
--- a/chrome/browser/chromeos/crostini/crostini_util.h
+++ b/chrome/browser/chromeos/crostini/crostini_util.h
@@ -46,6 +46,9 @@
 // UI uses this to indicate that crostini is available but disabled by policy.
 bool IsCrostiniUIAllowedForProfile(Profile* profile, bool check_policy = true);
 
+// Returns true if policy allows export import UI.
+bool IsCrostiniExportImportUIAllowedForProfile(Profile* profile);
+
 // Returns whether if Crostini has been enabled, i.e. the user has launched it
 // at least once and not deleted it.
 bool IsCrostiniEnabled(Profile* profile);
diff --git a/chrome/browser/chromeos/extensions/file_manager/file_stream_md5_digester.cc b/chrome/browser/chromeos/extensions/file_manager/file_stream_md5_digester.cc
index bc1b222..35b5ce7 100644
--- a/chrome/browser/chromeos/extensions/file_manager/file_stream_md5_digester.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/file_stream_md5_digester.cc
@@ -38,8 +38,8 @@
 void FileStreamMd5Digester::ReadNextChunk(const ResultCallback& callback) {
   const int result =
       reader_->Read(buffer_.get(), kMd5DigestBufferSize,
-                    base::Bind(&FileStreamMd5Digester::OnChunkRead,
-                               base::Unretained(this), callback));
+                    base::BindOnce(&FileStreamMd5Digester::OnChunkRead,
+                                   base::Unretained(this), callback));
   if (result != net::ERR_IO_PENDING)
     OnChunkRead(callback, result);
 }
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_dialog.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_dialog.cc
index 240592d..a1a90eb 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_dialog.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_dialog.cc
@@ -73,14 +73,10 @@
   }
 
   file_manager::util::GetSelectedFileInfo(
-      render_frame_host(),
-      GetProfile(),
-      file_paths,
-      option,
-      base::Bind(
+      render_frame_host(), GetProfile(), file_paths, option,
+      base::BindOnce(
           &FileManagerPrivateSelectFileFunction::GetSelectedFileInfoResponse,
-          this,
-          params->index));
+          this, params->index));
   return true;
 }
 
@@ -107,13 +103,11 @@
     file_urls.emplace_back(params->selected_paths[i]);
 
   file_manager::util::GetSelectedFileInfo(
-      render_frame_host(),
-      GetProfile(),
-      file_urls,
-      params->should_return_local_path ?
-          file_manager::util::NEED_LOCAL_PATH_FOR_OPENING :
-          file_manager::util::NO_LOCAL_PATH_RESOLUTION,
-      base::Bind(
+      render_frame_host(), GetProfile(), file_urls,
+      params->should_return_local_path
+          ? file_manager::util::NEED_LOCAL_PATH_FOR_OPENING
+          : file_manager::util::NO_LOCAL_PATH_RESOLUTION,
+      base::BindOnce(
           &FileManagerPrivateSelectFilesFunction::GetSelectedFileInfoResponse,
           this));
   return true;
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_drive.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_drive.cc
index f92e2b8..6d8e9a8d 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_drive.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_drive.cc
@@ -254,20 +254,20 @@
 
 class SingleEntryPropertiesGetterForDrive {
  public:
-  typedef base::Callback<void(std::unique_ptr<EntryProperties> properties,
-                              base::File::Error error)>
+  typedef base::OnceCallback<void(std::unique_ptr<EntryProperties> properties,
+                                  base::File::Error error)>
       ResultCallback;
 
   // Creates an instance and starts the process.
   static void Start(const base::FilePath local_path,
                     const std::set<EntryPropertyName>& names,
                     Profile* const profile,
-                    const ResultCallback& callback) {
+                    ResultCallback callback) {
     DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
     SingleEntryPropertiesGetterForDrive* instance =
         new SingleEntryPropertiesGetterForDrive(local_path, names, profile,
-                                                callback);
+                                                std::move(callback));
     instance->StartProcess();
 
     // The instance will be destroyed by itself.
@@ -280,8 +280,8 @@
       const base::FilePath local_path,
       const std::set<EntryPropertyName>& /* names */,
       Profile* const profile,
-      const ResultCallback& callback)
-      : callback_(callback),
+      ResultCallback callback)
+      : callback_(std::move(callback)),
         local_path_(local_path),
         running_profile_(profile),
         properties_(new EntryProperties),
@@ -415,13 +415,13 @@
     DCHECK_CURRENTLY_ON(BrowserThread::UI);
     DCHECK(!callback_.is_null());
 
-    callback_.Run(std::move(properties_),
-                  drive::FileErrorToBaseFileError(error));
+    std::move(callback_).Run(std::move(properties_),
+                             drive::FileErrorToBaseFileError(error));
     BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, this);
   }
 
   // Given parameters.
-  const ResultCallback callback_;
+  ResultCallback callback_;
   const base::FilePath local_path_;
   Profile* const running_profile_;
 
@@ -436,19 +436,19 @@
 
 class SingleEntryPropertiesGetterForFileSystemProvider {
  public:
-  typedef base::Callback<void(std::unique_ptr<EntryProperties> properties,
-                              base::File::Error error)>
+  typedef base::OnceCallback<void(std::unique_ptr<EntryProperties> properties,
+                                  base::File::Error error)>
       ResultCallback;
 
   // Creates an instance and starts the process.
   static void Start(const storage::FileSystemURL file_system_url,
                     const std::set<EntryPropertyName>& names,
-                    const ResultCallback& callback) {
+                    ResultCallback callback) {
     DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
     SingleEntryPropertiesGetterForFileSystemProvider* instance =
-        new SingleEntryPropertiesGetterForFileSystemProvider(file_system_url,
-                                                             names, callback);
+        new SingleEntryPropertiesGetterForFileSystemProvider(
+            file_system_url, names, std::move(callback));
     instance->StartProcess();
 
     // The instance will be destroyed by itself.
@@ -460,8 +460,8 @@
   SingleEntryPropertiesGetterForFileSystemProvider(
       const storage::FileSystemURL& file_system_url,
       const std::set<EntryPropertyName>& names,
-      const ResultCallback& callback)
-      : callback_(callback),
+      ResultCallback callback)
+      : callback_(std::move(callback)),
         file_system_url_(file_system_url),
         names_(names),
         properties_(new EntryProperties),
@@ -556,12 +556,12 @@
     DCHECK_CURRENTLY_ON(BrowserThread::UI);
     DCHECK(!callback_.is_null());
 
-    callback_.Run(std::move(properties_), result);
+    std::move(callback_).Run(std::move(properties_), result);
     BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, this);
   }
 
   // Given parameters.
-  const ResultCallback callback_;
+  ResultCallback callback_;
   const storage::FileSystemURL file_system_url_;
   const std::set<EntryPropertyName> names_;
 
@@ -887,16 +887,18 @@
       case storage::kFileSystemTypeDrive:
         SingleEntryPropertiesGetterForDrive::Start(
             file_system_url.path(), names_as_set, GetProfile(),
-            base::Bind(&FileManagerPrivateInternalGetEntryPropertiesFunction::
-                           CompleteGetEntryProperties,
-                       this, i, file_system_url));
+            base::BindOnce(
+                &FileManagerPrivateInternalGetEntryPropertiesFunction::
+                    CompleteGetEntryProperties,
+                this, i, file_system_url));
         break;
       case storage::kFileSystemTypeProvided:
         SingleEntryPropertiesGetterForFileSystemProvider::Start(
             file_system_url, names_as_set,
-            base::Bind(&FileManagerPrivateInternalGetEntryPropertiesFunction::
-                           CompleteGetEntryProperties,
-                       this, i, file_system_url));
+            base::BindOnce(
+                &FileManagerPrivateInternalGetEntryPropertiesFunction::
+                    CompleteGetEntryProperties,
+                this, i, file_system_url));
         break;
       case storage::kFileSystemTypeDriveFs:
         SingleEntryPropertiesGetterForDriveFs::Start(
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc
index 3132235..38767bb 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_file_system.cc
@@ -214,12 +214,12 @@
   storage::FileSystemOperationRunner::OperationID* operation_id =
       new storage::FileSystemOperationRunner::OperationID;
   *operation_id = file_system_context->operation_runner()->Copy(
-      source_url, destination_url,
-      storage::FileSystemOperation::OPTION_NONE,
+      source_url, destination_url, storage::FileSystemOperation::OPTION_NONE,
       storage::FileSystemOperation::ERROR_BEHAVIOR_ABORT,
-      base::Bind(&OnCopyProgress, profile_id, base::Unretained(operation_id)),
-      base::Bind(&OnCopyCompleted, profile_id, base::Owned(operation_id),
-                 source_url, destination_url));
+      base::BindRepeating(&OnCopyProgress, profile_id,
+                          base::Unretained(operation_id)),
+      base::BindOnce(&OnCopyCompleted, profile_id, base::Owned(operation_id),
+                     source_url, destination_url));
   return *operation_id;
 }
 
@@ -239,7 +239,7 @@
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 
   file_system_context->operation_runner()->Cancel(
-      operation_id, base::Bind(&OnCopyCancelled));
+      operation_id, base::BindOnce(&OnCopyCancelled));
 }
 
 // Converts a status code to a bool value and calls the |callback| with it.
@@ -511,7 +511,7 @@
     auto* manager = storage_monitor->media_transfer_protocol_manager();
     manager->GetStorageInfoFromDevice(
         storage_name,
-        base::Bind(
+        base::BindOnce(
             &FileManagerPrivateGetSizeStatsFunction::OnGetMtpAvailableSpace,
             this));
   } else {
@@ -595,11 +595,11 @@
 
   base::PostTaskWithTraitsAndReplyWithResult(
       FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
-      base::Bind(&GetFileNameMaxLengthAsync,
-                 file_system_url.path().AsUTF8Unsafe()),
-      base::Bind(&FileManagerPrivateInternalValidatePathNameLengthFunction::
-                     OnFilePathLimitRetrieved,
-                 this, params->name.size()));
+      base::BindOnce(&GetFileNameMaxLengthAsync,
+                     file_system_url.path().AsUTF8Unsafe()),
+      base::BindOnce(&FileManagerPrivateInternalValidatePathNameLengthFunction::
+                         OnFilePathLimitRetrieved,
+                     this, params->name.size()));
   return true;
 }
 
@@ -803,9 +803,9 @@
           GetProfile(), render_frame_host());
   const bool result = base::PostTaskWithTraitsAndReplyWithResult(
       FROM_HERE, {BrowserThread::IO},
-      base::Bind(&StartCopyOnIOThread, GetProfile(), file_system_context,
-                 source_url_, destination_url_),
-      base::Bind(
+      base::BindOnce(&StartCopyOnIOThread, GetProfile(), file_system_context,
+                     source_url_, destination_url_),
+      base::BindOnce(
           &FileManagerPrivateInternalStartCopyFunction::RunAfterStartCopy,
           this));
   if (!result)
@@ -1181,10 +1181,10 @@
 
   base::PostTaskWithTraitsAndReplyWithResult(
       FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
-      base::Bind(&base::ComputeDirectorySize, root_path),
-      base::Bind(&FileManagerPrivateInternalGetDirectorySizeFunction::
-                     OnDirectorySizeRetrieved,
-                 this));
+      base::BindOnce(&base::ComputeDirectorySize, root_path),
+      base::BindOnce(&FileManagerPrivateInternalGetDirectorySizeFunction::
+                         OnDirectorySizeRetrieved,
+                     this));
   return true;
 }
 
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
index 14c2787..e06dd54c 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
@@ -608,7 +608,7 @@
           service->GetProvidedFileSystem(volume->provider_id(),
                                          volume->file_system_id());
       if (file_system)
-        file_system->Configure(base::Bind(
+        file_system->Configure(base::BindOnce(
             &FileManagerPrivateConfigureVolumeFunction::OnCompleted, this));
       break;
     }
@@ -946,7 +946,7 @@
   DCHECK(file_system);
   file_system->ExecuteAction(
       paths, params->action_id,
-      base::Bind(
+      base::BindOnce(
           &FileManagerPrivateInternalExecuteCustomActionFunction::OnCompleted,
           this));
   return RespondLater();
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_mount.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_mount.cc
index 71a85ef..c6575c5 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_mount.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_mount.cc
@@ -37,15 +37,15 @@
 // Does chmod o+r for the given path to ensure the file is readable from avfs.
 void EnsureReadableFilePermissionAsync(
     const base::FilePath& path,
-    const base::Callback<void(drive::FileError, const base::FilePath&)>&
+    base::OnceCallback<void(drive::FileError, const base::FilePath&)>
         callback) {
   int mode = 0;
   if (!base::GetPosixFilePermissions(path, &mode) ||
       !base::SetPosixFilePermissions(path, mode | S_IROTH)) {
-    callback.Run(drive::FILE_ERROR_ACCESS_DENIED, base::FilePath());
+    std::move(callback).Run(drive::FILE_ERROR_ACCESS_DENIED, base::FilePath());
     return;
   }
-  callback.Run(drive::FILE_ERROR_OK, path);
+  std::move(callback).Run(drive::FILE_ERROR_OK, path);
 }
 
 }  // namespace
@@ -80,9 +80,9 @@
     const base::FilePath drive_path = drive::util::ExtractDrivePath(path);
     file_system->GetFile(
         drive_path,
-        base::Bind(&FileManagerPrivateAddMountFunction::RunAfterGetDriveFile,
-                   this,
-                   drive_path));
+        base::BindOnce(
+            &FileManagerPrivateAddMountFunction::RunAfterGetDriveFile, this,
+            drive_path));
   } else {
     file_manager::VolumeManager* volume_manager =
         file_manager::VolumeManager::Get(GetProfile());
@@ -105,10 +105,10 @@
       base::PostTaskWithTraits(
           FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
           base::BindOnce(&EnsureReadableFilePermissionAsync, path,
-                         google_apis::CreateRelayCallback(
-                             base::Bind(&FileManagerPrivateAddMountFunction::
-                                            RunAfterMarkCacheFileAsMounted,
-                                        this, path.BaseName()))));
+                         google_apis::CreateRelayCallback(base::BindOnce(
+                             &FileManagerPrivateAddMountFunction::
+                                 RunAfterMarkCacheFileAsMounted,
+                             this, path.BaseName()))));
     } else {
       RunAfterMarkCacheFileAsMounted(
           path.BaseName(), drive::FILE_ERROR_OK, path);
@@ -285,7 +285,7 @@
   const base::FilePath drive_path = drive::util::ExtractDrivePath(path);
   file_system->GetFile(
       drive_path,
-      base::Bind(
+      base::BindOnce(
           &FileManagerPrivateMarkCacheAsMountedFunction::RunAfterGetDriveFile,
           this, drive_path, is_mounted));
   return true;
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_tasks.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_tasks.cc
index 97711b0..d0f6af23 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_tasks.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_tasks.cc
@@ -107,8 +107,9 @@
 
   const bool result = file_manager::file_tasks::ExecuteFileTask(
       GetProfile(), source_url(), task, urls,
-      base::Bind(&FileManagerPrivateInternalExecuteTaskFunction::OnTaskExecuted,
-                 this));
+      base::BindOnce(
+          &FileManagerPrivateInternalExecuteTaskFunction::OnTaskExecuted,
+          this));
   if (!result) {
     results_ =
         Create(extensions::api::file_manager_private::TASK_RESULT_FAILED);
@@ -188,7 +189,7 @@
 
   file_manager::file_tasks::FindAllTypesOfTasks(
       GetProfile(), entries, urls_,
-      base::Bind(
+      base::BindOnce(
           &FileManagerPrivateInternalGetFileTasksFunction::OnFileTasksListed,
           this));
 }
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_util.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_util.cc
index c4c6a13..d67bbc2e 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_util.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_util.cc
@@ -52,61 +52,62 @@
 
 // The callback type for GetFileNativeLocalPathFor{Opening,Saving}. It receives
 // the resolved local path when successful, and receives empty path for failure.
-typedef base::Callback<void(const base::FilePath&)> LocalPathCallback;
+typedef base::OnceCallback<void(const base::FilePath&)> LocalPathCallback;
 
 // Converts a callback from Drive file system to LocalPathCallback.
 void OnDriveGetFile(const base::FilePath& path,
-                    const LocalPathCallback& callback,
+                    LocalPathCallback callback,
                     drive::FileError error,
                     const base::FilePath& local_file_path,
                     std::unique_ptr<drive::ResourceEntry> entry) {
   if (error != drive::FILE_ERROR_OK)
     DLOG(ERROR) << "Failed to get " << path.value() << " with: " << error;
-  callback.Run(local_file_path);
+  std::move(callback).Run(local_file_path);
 }
 
 // Gets a resolved local file path of a non native |path| for file opening.
 void GetFileNativeLocalPathForOpening(Profile* profile,
                                       const base::FilePath& path,
-                                      const LocalPathCallback& callback) {
+                                      LocalPathCallback callback) {
   if (drive::util::IsUnderDriveMountPoint(path)) {
     drive::FileSystemInterface* file_system =
         drive::util::GetFileSystemByProfile(profile);
     if (!file_system) {
       DLOG(ERROR) << "Drive file selected while disabled: " << path.value();
-      callback.Run(base::FilePath());
+      std::move(callback).Run(base::FilePath());
       return;
     }
-    file_system->GetFile(drive::util::ExtractDrivePath(path),
-                         base::BindOnce(&OnDriveGetFile, path, callback));
+    file_system->GetFile(
+        drive::util::ExtractDrivePath(path),
+        base::BindOnce(&OnDriveGetFile, path, std::move(callback)));
     return;
   }
 
   VolumeManager::Get(profile)->snapshot_manager()->CreateManagedSnapshot(
-      path, callback);
+      path, std::move(callback));
 }
 
 // Gets a resolved local file path of a non native |path| for file saving.
 void GetFileNativeLocalPathForSaving(Profile* profile,
                                      const base::FilePath& path,
-                                     const LocalPathCallback& callback) {
+                                     LocalPathCallback callback) {
   if (drive::util::IsUnderDriveMountPoint(path)) {
     drive::FileSystemInterface* file_system =
         drive::util::GetFileSystemByProfile(profile);
     if (!file_system) {
       DLOG(ERROR) << "Drive file selected while disabled: " << path.value();
-      callback.Run(base::FilePath());
+      std::move(callback).Run(base::FilePath());
       return;
     }
     file_system->GetFileForSaving(
         drive::util::ExtractDrivePath(path),
-        base::BindOnce(&OnDriveGetFile, path, callback));
+        base::BindOnce(&OnDriveGetFile, path, std::move(callback)));
     return;
   }
 
   // TODO(kinaba): For now, the only writable non-local volume is Drive.
   NOTREACHED();
-  callback.Run(base::FilePath());
+  std::move(callback).Run(base::FilePath());
 }
 
 // Forward declarations of helper functions for GetSelectedFileInfo().
@@ -150,20 +151,16 @@
         }
         case NEED_LOCAL_PATH_FOR_OPENING: {
           GetFileNativeLocalPathForOpening(
-              profile,
-              file_path,
-              base::Bind(&ContinueGetSelectedFileInfo,
-                         profile,
-                         base::Passed(&params)));
+              profile, file_path,
+              base::BindOnce(&ContinueGetSelectedFileInfo, profile,
+                             std::move(params)));
           return;  // Remaining work is done in ContinueGetSelectedFileInfo.
         }
         case NEED_LOCAL_PATH_FOR_SAVING: {
           GetFileNativeLocalPathForSaving(
-              profile,
-              file_path,
-              base::Bind(&ContinueGetSelectedFileInfo,
-                         profile,
-                         base::Passed(&params)));
+              profile, file_path,
+              base::BindOnce(&ContinueGetSelectedFileInfo, profile,
+                             std::move(params)));
           return;  // Remaining work is done in ContinueGetSelectedFileInfo.
         }
       }
@@ -188,7 +185,7 @@
       params->selected_files.emplace_back(file_path, file_path);
     }
   }
-  params->callback.Run(params->selected_files);
+  std::move(params->callback).Run(params->selected_files);
 }
 
 // Part of GetSelectedFileInfo().
@@ -197,7 +194,7 @@
     std::unique_ptr<GetSelectedFileInfoParams> params,
     const base::FilePath& local_path) {
   if (local_path.empty()) {
-    params->callback.Run(std::vector<ui::SelectedFileInfo>());
+    std::move(params->callback).Run(std::vector<ui::SelectedFileInfo>());
     return;
   }
   const int index = params->selected_files.size();
@@ -432,7 +429,7 @@
   std::unique_ptr<GetSelectedFileInfoParams> params(
       new GetSelectedFileInfoParams);
   params->local_path_option = local_path_option;
-  params->callback = callback;
+  params->callback = std::move(callback);
 
   for (size_t i = 0; i < file_urls.size(); ++i) {
     const GURL& file_url = file_urls[i];
@@ -445,8 +442,8 @@
   }
 
   base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(&GetSelectedFileInfoInternal, profile,
-                                base::Passed(&params)));
+      FROM_HERE,
+      base::BindOnce(&GetSelectedFileInfoInternal, profile, std::move(params)));
 }
 
 void SetupProfileFileAccessPermissions(int render_view_process_id,
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_util.h b/chrome/browser/chromeos/extensions/file_manager/private_api_util.h
index f630142..2d288ef 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_util.h
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_util.h
@@ -69,7 +69,7 @@
                                    const GURL& url);
 
 // The callback type is used for GetSelectedFileInfo().
-typedef base::Callback<void(const std::vector<ui::SelectedFileInfo>&)>
+typedef base::OnceCallback<void(const std::vector<ui::SelectedFileInfo>&)>
     GetSelectedFileInfoCallback;
 
 // Option enum to control how to set the ui::SelectedFileInfo::local_path
diff --git a/chrome/browser/chromeos/file_manager/path_util.cc b/chrome/browser/chromeos/file_manager/path_util.cc
index 24846573..7e615476 100644
--- a/chrome/browser/chromeos/file_manager/path_util.cc
+++ b/chrome/browser/chromeos/file_manager/path_util.cc
@@ -61,9 +61,11 @@
     FILE_PATH_LITERAL("/download");
 constexpr base::FilePath::CharType kArcExternalFilesRoot[] =
     FILE_PATH_LITERAL("/external_files");
-// Sync with the removable media provider in ARC++ side.
-constexpr char kArcRemovableMediaProviderUrl[] =
-    "content://org.chromium.arc.removablemediaprovider/";
+// Sync with the volume provider in ARC++ side.
+constexpr char kArcRemovableMediaContentUrlPrefix[] =
+    "content://org.chromium.arc.volumeprovider/removable/";
+constexpr char kArcMyFilesContentUrlPrefix[] =
+    "content://org.chromium.arc.volumeprovider/MyFiles/";
 
 Profile* GetPrimaryProfile() {
   if (!user_manager::UserManager::IsInitialized())
@@ -385,7 +387,15 @@
   base::FilePath relative_path;
   if (base::FilePath(kRemovableMediaPath)
           .AppendRelativePath(path, &relative_path)) {
-    *arc_url_out = GURL(kArcRemovableMediaProviderUrl)
+    *arc_url_out = GURL(kArcRemovableMediaContentUrlPrefix)
+                       .Resolve(net::EscapePath(relative_path.AsUTF8Unsafe()));
+    return true;
+  }
+
+  // Convert paths under MyFiles
+  if (base::FilePath(GetMyFilesFolderForProfile(primary_profile))
+          .AppendRelativePath(path, &relative_path)) {
+    *arc_url_out = GURL(kArcMyFilesContentUrlPrefix)
                        .Resolve(net::EscapePath(relative_path.AsUTF8Unsafe()));
     return true;
   }
diff --git a/chrome/browser/chromeos/file_manager/path_util_unittest.cc b/chrome/browser/chromeos/file_manager/path_util_unittest.cc
index fa85091..3ed556e 100644
--- a/chrome/browser/chromeos/file_manager/path_util_unittest.cc
+++ b/chrome/browser/chromeos/file_manager/path_util_unittest.cc
@@ -697,7 +697,21 @@
   GURL url;
   EXPECT_TRUE(ConvertPathToArcUrl(
       base::FilePath::FromUTF8Unsafe("/media/removable/a/b/c"), &url));
-  EXPECT_EQ(GURL("content://org.chromium.arc.removablemediaprovider/a/b/c"),
+  EXPECT_EQ(GURL("content://org.chromium.arc.volumeprovider/removable/a/b/c"),
+            url);
+}
+
+TEST_F(FileManagerPathUtilConvertUrlTest, ConvertPathToArcUrl_MyFiles) {
+  chromeos::ScopedSetRunningOnChromeOSForTesting fake_release(kLsbRelease,
+                                                              base::Time());
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(chromeos::features::kMyFilesVolume);
+  GURL url;
+  const base::FilePath myfiles = GetMyFilesFolderForProfile(
+      chromeos::ProfileHelper::Get()->GetProfileByUserIdHashForTest(
+          "user@gmail.com-hash"));
+  EXPECT_TRUE(ConvertPathToArcUrl(myfiles.AppendASCII("a/b/c"), &url));
+  EXPECT_EQ(GURL("content://org.chromium.arc.volumeprovider/MyFiles/a/b/c"),
             url);
 }
 
@@ -769,8 +783,32 @@
           [](base::RunLoop* run_loop, const std::vector<GURL>& urls) {
             run_loop->Quit();
             ASSERT_EQ(1U, urls.size());
+            EXPECT_EQ(GURL("content://org.chromium.arc.volumeprovider/"
+                           "removable/a/b/c"),
+                      urls[0]);
+          },
+          &run_loop));
+  run_loop.Run();
+}
+
+TEST_F(FileManagerPathUtilConvertUrlTest, ConvertToContentUrls_MyFiles) {
+  chromeos::ScopedSetRunningOnChromeOSForTesting fake_release(kLsbRelease,
+                                                              base::Time());
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(chromeos::features::kMyFilesVolume);
+  const base::FilePath myfiles = GetMyFilesFolderForProfile(
+      chromeos::ProfileHelper::Get()->GetProfileByUserIdHashForTest(
+          "user@gmail.com-hash"));
+  base::RunLoop run_loop;
+  ConvertToContentUrls(
+      std::vector<FileSystemURL>{
+          CreateExternalURL(myfiles.AppendASCII("a/b/c"))},
+      base::BindOnce(
+          [](base::RunLoop* run_loop, const std::vector<GURL>& urls) {
+            run_loop->Quit();
+            ASSERT_EQ(1U, urls.size());
             EXPECT_EQ(
-                GURL("content://org.chromium.arc.removablemediaprovider/a/b/c"),
+                GURL("content://org.chromium.arc.volumeprovider/MyFiles/a/b/c"),
                 urls[0]);
           },
           &run_loop));
@@ -953,9 +991,9 @@
             run_loop->Quit();
             ASSERT_EQ(4U, urls.size());
             EXPECT_EQ(GURL(), urls[0]);  // Invalid URL.
-            EXPECT_EQ(
-                GURL("content://org.chromium.arc.removablemediaprovider/a/b/c"),
-                urls[1]);
+            EXPECT_EQ(GURL("content://org.chromium.arc.volumeprovider/"
+                           "removable/a/b/c"),
+                      urls[1]);
             EXPECT_EQ(GURL("content://org.chromium.arc.chromecontentprovider/"
                            "externalfile%3Adrive-user%252540gmail.com-hash%2Fa%"
                            "2Fb%2Fc"),
diff --git a/chrome/browser/chromeos/login/enrollment/enrollment_screen_browsertest.cc b/chrome/browser/chromeos/login/enrollment/enrollment_screen_browsertest.cc
index 693c95e..a359d11d9 100644
--- a/chrome/browser/chromeos/login/enrollment/enrollment_screen_browsertest.cc
+++ b/chrome/browser/chromeos/login/enrollment/enrollment_screen_browsertest.cc
@@ -35,7 +35,7 @@
   EnrollmentScreenTest() = default;
   ~EnrollmentScreenTest() override = default;
 
-  // Overridden from InProcessBrowserTest:
+  // InProcessBrowserTest:
   void SetUpCommandLine(base::CommandLine* command_line) override {
     InProcessBrowserTest::SetUpCommandLine(command_line);
     command_line->AppendArg(switches::kLoginManager);
@@ -81,8 +81,7 @@
   run_loop.Run();
 }
 
-// Flaky test: crbug.com/394069
-IN_PROC_BROWSER_TEST_F(EnrollmentScreenTest, DISABLED_TestSuccess) {
+IN_PROC_BROWSER_TEST_F(EnrollmentScreenTest, TestSuccess) {
   WizardController::SkipEnrollmentPromptsForTesting();
   base::RunLoop run_loop;
   EXPECT_FALSE(StartupUtils::IsDeviceRegistered());
@@ -100,7 +99,7 @@
   ~AttestationAuthEnrollmentScreenTest() override = default;
 
  private:
-  // Overridden from EnrollmentScreenTest:
+  // EnrollmentScreenTest:
   void SetUpCommandLine(base::CommandLine* command_line) override {
     EnrollmentScreenTest::SetUpCommandLine(command_line);
     command_line->AppendSwitch(switches::kEnterpriseEnableZeroTouchEnrollment);
@@ -151,7 +150,7 @@
   ~ForcedAttestationAuthEnrollmentScreenTest() override = default;
 
  private:
-  // Overridden from EnrollmentScreenTest:
+  // EnrollmentScreenTest:
   void SetUpCommandLine(base::CommandLine* command_line) override {
     EnrollmentScreenTest::SetUpCommandLine(command_line);
     command_line->AppendSwitchASCII(
@@ -177,7 +176,7 @@
   ~MultiAuthEnrollmentScreenTest() override = default;
 
  private:
-  // Overridden from EnrollmentScreenTest:
+  // EnrollmentScreenTest:
   void SetUpCommandLine(base::CommandLine* command_line) override {
     EnrollmentScreenTest::SetUpCommandLine(command_line);
     command_line->AppendSwitch(switches::kEnterpriseEnableZeroTouchEnrollment);
@@ -209,7 +208,7 @@
   ~ProvisionedEnrollmentScreenTest() override = default;
 
  private:
-  // Overridden from EnrollmentScreenTest:
+  // EnrollmentScreenTest:
   void SetUpCommandLine(base::CommandLine* command_line) override {
     EnrollmentScreenTest::SetUpCommandLine(command_line);
     base::FilePath test_data_dir;
diff --git a/chrome/browser/chromeos/login/existing_user_controller.cc b/chrome/browser/chromeos/login/existing_user_controller.cc
index aab985b..c9241c3 100644
--- a/chrome/browser/chromeos/login/existing_user_controller.cc
+++ b/chrome/browser/chromeos/login/existing_user_controller.cc
@@ -673,14 +673,12 @@
 void ExistingUserController::OnSigninScreenReady() {
   // Used to debug crbug.com/902315. Feel free to remove after that is fixed.
   VLOG(1) << "OnSigninScreenReady";
-  auto_launch_ready_ = true;
   StartAutoLoginTimer();
 }
 
 void ExistingUserController::OnGaiaScreenReady() {
   // Used to debug crbug.com/902315. Feel free to remove after that is fixed.
   VLOG(1) << "OnGaiaScreenReady";
-  auto_launch_ready_ = true;
   StartAutoLoginTimer();
 }
 
@@ -1466,7 +1464,7 @@
 }
 
 void ExistingUserController::OnPublicSessionAutoLoginTimerFire() {
-  CHECK(auto_launch_ready_ && public_session_auto_login_account_id_.is_valid());
+  CHECK(public_session_auto_login_account_id_.is_valid());
   VLOG(2) << "Public session autologin fired";
   SigninSpecifics signin_specifics;
   signin_specifics.is_auto_login = true;
@@ -1476,7 +1474,7 @@
 }
 
 void ExistingUserController::OnArcKioskAutoLoginTimerFire() {
-  CHECK(auto_launch_ready_ && (arc_kiosk_auto_login_account_id_.is_valid()));
+  CHECK(arc_kiosk_auto_login_account_id_.is_valid());
   VLOG(2) << "ARC kiosk autologin fired";
   SigninSpecifics signin_specifics;
   signin_specifics.is_auto_login = true;
@@ -1515,11 +1513,10 @@
 }
 
 void ExistingUserController::StartAutoLoginTimer() {
-  if (!auto_launch_ready_ || is_login_in_progress_ ||
+  if (is_login_in_progress_ ||
       (!public_session_auto_login_account_id_.is_valid() &&
        !arc_kiosk_auto_login_account_id_.is_valid())) {
     VLOG(2) << "Not starting autologin timer, because:";
-    VLOG_IF(2, !auto_launch_ready_) << "* Not ready;";
     VLOG_IF(2, is_login_in_progress_) << "* Login is in process;";
     VLOG_IF(2, (!public_session_auto_login_account_id_.is_valid() &&
                 !arc_kiosk_auto_login_account_id_.is_valid()))
diff --git a/chrome/browser/chromeos/login/existing_user_controller.h b/chrome/browser/chromeos/login/existing_user_controller.h
index 5c3352a2..4cb575d 100644
--- a/chrome/browser/chromeos/login/existing_user_controller.h
+++ b/chrome/browser/chromeos/login/existing_user_controller.h
@@ -377,10 +377,6 @@
   LoginPerformer::AuthorizationMode auth_mode_ =
       LoginPerformer::AUTH_MODE_EXTENSION;
 
-  // When the sign-in or GAIA UI is finished loading
-  // public session or ARC kiosk are ready to auto-launch.
-  bool auto_launch_ready_ = false;
-
   // Indicates use of local (not GAIA) authentication.
   bool auth_flow_offline_ = false;
 
diff --git a/chrome/browser/chromeos/login/existing_user_controller_auto_login_unittest.cc b/chrome/browser/chromeos/login/existing_user_controller_auto_login_unittest.cc
index 487b42a..4883d7c 100644
--- a/chrome/browser/chromeos/login/existing_user_controller_auto_login_unittest.cc
+++ b/chrome/browser/chromeos/login/existing_user_controller_auto_login_unittest.cc
@@ -151,15 +151,10 @@
 };
 
 TEST_F(ExistingUserControllerAutoLoginTest, StartAutoLoginTimer) {
-  // Timer shouldn't start until signin screen is ready.
-  set_auto_login_account_id(auto_login_account_id_);
   set_auto_login_delay(kAutoLoginDelay2);
-  existing_user_controller()->StartAutoLoginTimer();
-  EXPECT_FALSE(auto_login_timer());
 
   // Timer shouldn't start if the policy isn't set.
   set_auto_login_account_id(EmptyAccountId());
-  existing_user_controller()->OnSigninScreenReady();
   existing_user_controller()->StartAutoLoginTimer();
   EXPECT_FALSE(auto_login_timer());
 
@@ -179,7 +174,6 @@
 }
 
 TEST_F(ExistingUserControllerAutoLoginTest, StopAutoLoginTimer) {
-  existing_user_controller()->OnSigninScreenReady();
   set_auto_login_account_id(auto_login_account_id_);
   set_auto_login_delay(kAutoLoginDelay2);
 
@@ -193,7 +187,6 @@
 }
 
 TEST_F(ExistingUserControllerAutoLoginTest, ResetAutoLoginTimer) {
-  existing_user_controller()->OnSigninScreenReady();
   set_auto_login_account_id(auto_login_account_id_);
 
   // Timer starts off not running.
@@ -222,8 +215,6 @@
 }
 
 TEST_F(ExistingUserControllerAutoLoginTest, ConfigureAutoLogin) {
-  existing_user_controller()->OnSigninScreenReady();
-
   // Timer shouldn't start when the policy is disabled.
   ConfigureAutoLogin();
   EXPECT_FALSE(auto_login_timer());
diff --git a/chrome/browser/client_hints/client_hints.cc b/chrome/browser/client_hints/client_hints.cc
index a72ae92..09f73ed5 100644
--- a/chrome/browser/client_hints/client_hints.cc
+++ b/chrome/browser/client_hints/client_hints.cc
@@ -12,8 +12,10 @@
 #include "base/metrics/field_trial_params.h"
 #include "base/rand_util.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/chrome_content_browser_client.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/client_hints/client_hints.h"
@@ -24,6 +26,7 @@
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/common/content_features.h"
+#include "content/public/common/content_switches.h"
 #include "content/public/common/origin_util.h"
 #include "net/base/url_util.h"
 #include "net/http/http_request_headers.h"
@@ -157,6 +160,12 @@
   return effective_connection_type;
 }
 
+bool UserAgentClientHintEnabled() {
+  return base::FeatureList::IsEnabled(features::kUserAgentClientHint) ||
+         base::CommandLine::ForCurrentProcess()->HasSwitch(
+             switches::kEnableExperimentalWebPlatformFeatures);
+}
+
 }  // namespace
 
 namespace client_hints {
@@ -378,12 +387,53 @@
             profile->GetPrefs()->GetString(language::prefs::kAcceptLanguages)));
   }
 
+  if (UserAgentClientHintEnabled()) {
+    blink::UserAgentMetadata ua = ::GetUserAgentMetadata();
+
+    // The `Sec-CH-UA` client hint is attached to all outgoing requests. The
+    // opt-in controls the header's value, not its presence. This is
+    // (intentionally) different than other client hints.
+    //
+    // https://tools.ietf.org/html/draft-west-ua-client-hints-00#section-2.4
+    additional_headers->SetHeader(
+        blink::kClientHintsHeaderMapping[static_cast<int>(
+            blink::mojom::WebClientHintsType::kUA)],
+        // TODO(mkwst): This should include only the major version if the
+        // recipient hasn't opted into the hint.
+        ua.version.empty() ? ua.brand.c_str()
+                           : base::StringPrintf("%s %s", ua.brand.c_str(),
+                                                ua.version.c_str()));
+
+    if (web_client_hints.IsEnabled(blink::mojom::WebClientHintsType::kUAArch)) {
+      additional_headers->SetHeader(
+          blink::kClientHintsHeaderMapping[static_cast<int>(
+              blink::mojom::WebClientHintsType::kUAArch)],
+          ua.architecture);
+    }
+
+    if (web_client_hints.IsEnabled(
+            blink::mojom::WebClientHintsType::kUAPlatform)) {
+      additional_headers->SetHeader(
+          blink::kClientHintsHeaderMapping[static_cast<int>(
+              blink::mojom::WebClientHintsType::kUAPlatform)],
+          ua.platform);
+    }
+
+    if (web_client_hints.IsEnabled(
+            blink::mojom::WebClientHintsType::kUAModel)) {
+      additional_headers->SetHeader(
+          blink::kClientHintsHeaderMapping[static_cast<int>(
+              blink::mojom::WebClientHintsType::kUAModel)],
+          ua.model);
+    }
+  }
+
   // Static assert that triggers if a new client hint header is added. If a
   // new client hint header is added, the following assertion should be updated.
   // If possible, logic should be added above so that the request headers for
   // the newly added client hint can be added to the request.
   static_assert(
-      blink::mojom::WebClientHintsType::kLang ==
+      blink::mojom::WebClientHintsType::kUAModel ==
           blink::mojom::WebClientHintsType::kMaxValue,
       "Consider adding client hint request headers from the browser process");
 
diff --git a/chrome/browser/client_hints/client_hints_browsertest.cc b/chrome/browser/client_hints/client_hints_browsertest.cc
index 39ba63c..e7ce545f 100644
--- a/chrome/browser/client_hints/client_hints_browsertest.cc
+++ b/chrome/browser/client_hints/client_hints_browsertest.cc
@@ -4,6 +4,7 @@
 
 #include <cctype>
 
+#include "base/base_switches.h"
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/metrics/field_trial_param_associator.h"
@@ -138,6 +139,7 @@
         https_cross_origin_server_(net::EmbeddedTestServer::TYPE_HTTPS),
         expect_client_hints_on_main_frame_(false),
         expect_client_hints_on_subresources_(false),
+        count_user_agent_hint_headers_seen_(0),
         count_client_hints_headers_seen_(0),
         request_interceptor_(nullptr) {
     http_server_.ServeFilesFromSourceDirectory("chrome/test/data/client_hints");
@@ -234,6 +236,17 @@
 
   ~ClientHintsBrowserTest() override {}
 
+  virtual std::unique_ptr<base::FeatureList> EnabledFeatures() {
+    std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
+    feature_list->InitializeFromCommandLine("UserAgentClientHint", "");
+    return feature_list;
+  }
+
+  void SetUp() override {
+    scoped_feature_list_.InitWithFeatureList(EnabledFeatures());
+    InProcessBrowserTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
     host_resolver()->AddRule("*", "127.0.0.1");
 
@@ -369,6 +382,10 @@
 
   const GURL& redirect_url() const { return redirect_url_; }
 
+  size_t count_user_agent_hint_headers_seen() const {
+    return count_user_agent_hint_headers_seen_;
+  }
+
   size_t count_client_hints_headers_seen() const {
     return count_client_hints_headers_seen_;
   }
@@ -536,7 +553,12 @@
     for (size_t i = 0; i < blink::kClientHintsMappingsCount; ++i) {
       if (base::ContainsKey(request.headers,
                             blink::kClientHintsHeaderMapping[i])) {
-        count_client_hints_headers_seen_++;
+        // The user agent hint is special:
+        if (std::string(blink::kClientHintsHeaderMapping[i]) == "sec-ch-ua") {
+          count_user_agent_hint_headers_seen_++;
+        } else {
+          count_client_hints_headers_seen_++;
+        }
       }
     }
   }
@@ -550,6 +572,12 @@
       if (std::string(blink::kClientHintsHeaderMapping[i]) == "width") {
         continue;
       }
+
+      // `Sec-CH-UA` is attached on all requests.
+      if (std::string(blink::kClientHintsHeaderMapping[i]) == "sec-ch-ua") {
+        continue;
+      }
+
       EXPECT_EQ(expect_client_hints,
                 base::ContainsKey(request.headers,
                                   blink::kClientHintsHeaderMapping[i]));
@@ -650,6 +678,7 @@
   // Expect client hints on all the subresource requests.
   bool expect_client_hints_on_subresources_;
 
+  size_t count_user_agent_hint_headers_seen_;
   size_t count_client_hints_headers_seen_;
 
   std::unique_ptr<ThirdPartyURLLoaderInterceptor> request_interceptor_;
@@ -669,10 +698,11 @@
                          testing::Bool());
 
 class ClientHintsAllowThirdPartyBrowserTest : public ClientHintsBrowserTest {
-  void SetUpCommandLine(base::CommandLine* cmd) override {
-    scoped_feature_list_.InitFromCommandLine("AllowClientHintsToThirdParty",
-                                             "");
-    ClientHintsBrowserTest::SetUpCommandLine(cmd);
+  std::unique_ptr<base::FeatureList> EnabledFeatures() override {
+    std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
+    feature_list->InitializeFromCommandLine(
+        "AllowClientHintsToThirdParty,UserAgentClientHint", "");
+    return feature_list;
   }
 };
 
@@ -708,8 +738,8 @@
   content::FetchHistogramsFromChildProcesses();
   SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
 
-  // client_hints_url() sets seven client hints.
-  histogram_tester.ExpectUniqueSample("ClientHints.UpdateSize", 7, 1);
+  // client_hints_url() sets eleven client hints.
+  histogram_tester.ExpectUniqueSample("ClientHints.UpdateSize", 11, 1);
   // accept_ch_with_lifetime_url() sets client hints persist duration to 3600
   // seconds.
   histogram_tester.ExpectUniqueSample("ClientHints.PersistDuration",
@@ -764,9 +794,12 @@
   content::FetchHistogramsFromChildProcesses();
   SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
 
-  // seven client hints are attached to the image request, and seven to the main
-  // frame request.
-  EXPECT_EQ(14u, count_client_hints_headers_seen());
+  // The user agent hint is attached to all three requests:
+  EXPECT_EQ(3u, count_user_agent_hint_headers_seen());
+
+  // Ten client hints are attached to the image request, and ten to the
+  // main frame request.
+  EXPECT_EQ(20u, count_client_hints_headers_seen());
 
   // Navigating to without_accept_ch_without_lifetime_img_foo_com() should not
   // attach client hints to the image subresouce contained in that page since
@@ -779,13 +812,15 @@
 
   // The device-memory and dprheader is attached to the main frame request.
 #if defined(OS_ANDROID)
-  EXPECT_EQ(7u, count_client_hints_headers_seen());
+  EXPECT_EQ(10u, count_client_hints_headers_seen());
 #else
-  EXPECT_EQ(21u, count_client_hints_headers_seen());
+  EXPECT_EQ(30u, count_client_hints_headers_seen());
 #endif
-  // Requests to third party servers should not have client hints attached.
+
+  // Requests to third party servers should have only one client hint attached
+  // (`Sec-CH-UA`).
   EXPECT_EQ(1u, third_party_request_count_seen());
-  EXPECT_EQ(0u, third_party_client_hints_count_seen());
+  EXPECT_EQ(1u, third_party_client_hints_count_seen());
 }
 
 // Test that client hints are attached to third party subresources if
@@ -805,7 +840,7 @@
   ui_test_utils::NavigateToURL(browser(), gurl);
   histogram_tester.ExpectTotalCount("ClientHints.UpdateEventCount", 0);
 
-  EXPECT_EQ(7u, count_client_hints_headers_seen());
+  EXPECT_EQ(11u, count_client_hints_headers_seen());
 
   // Requests to third party servers should not have client hints attached.
   EXPECT_EQ(1u, third_party_request_count_seen());
@@ -832,14 +867,16 @@
   ui_test_utils::NavigateToURL(browser(), gurl);
   histogram_tester.ExpectTotalCount("ClientHints.UpdateEventCount", 0);
 
-  EXPECT_EQ(7u, count_client_hints_headers_seen());
+  EXPECT_EQ(2u, count_user_agent_hint_headers_seen());
+  EXPECT_EQ(10u, count_client_hints_headers_seen());
 
   // Requests to third party servers should not have client hints attached.
   EXPECT_EQ(1u, third_party_request_count_seen());
 
   // Client hints should not be sent to the third-party when feature
-  // "AllowClientHintsToThirdParty" is not enabled.
-  EXPECT_EQ(0u, third_party_client_hints_count_seen());
+  // "AllowClientHintsToThirdParty" is not enabled, with the exception of the
+  // `Sec-CH-UA` hint, which is sent with every request.
+  EXPECT_EQ(1u, third_party_client_hints_count_seen());
 }
 
 // Loads a HTTPS webpage that does not request persisting of client hints.
@@ -1001,7 +1038,7 @@
   content::FetchHistogramsFromChildProcesses();
   SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
 
-  histogram_tester.ExpectUniqueSample("ClientHints.UpdateSize", 7, 1);
+  histogram_tester.ExpectUniqueSample("ClientHints.UpdateSize", 11u, 1);
   // |gurl| sets client hints persist duration to 3600 seconds.
   histogram_tester.ExpectUniqueSample("ClientHints.PersistDuration",
                                       3600 * 1000, 1);
@@ -1019,9 +1056,12 @@
   ui_test_utils::NavigateToURL(browser(),
                                without_accept_ch_without_lifetime_local_url());
 
-  // seven client hints are attached to the image request, and seven to the main
-  // frame request.
-  EXPECT_EQ(14u, count_client_hints_headers_seen());
+  // The user agent hint is attached to all three requests:
+  EXPECT_EQ(3u, count_user_agent_hint_headers_seen());
+
+  // Ten client hints are attached to the image request, and ten to the
+  // main frame request.
+  EXPECT_EQ(20u, count_client_hints_headers_seen());
 }
 
 // Loads a webpage that does not request persisting of client hints.
@@ -1061,7 +1101,7 @@
   content::FetchHistogramsFromChildProcesses();
   SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
 
-  histogram_tester.ExpectUniqueSample("ClientHints.UpdateSize", 7, 1);
+  histogram_tester.ExpectUniqueSample("ClientHints.UpdateSize", 11u, 1);
   // accept_ch_with_lifetime_url() sets client hints persist duration to 3600
   // seconds.
   histogram_tester.ExpectUniqueSample("ClientHints.PersistDuration",
@@ -1079,9 +1119,12 @@
   ui_test_utils::NavigateToURL(browser(),
                                without_accept_ch_without_lifetime_url());
 
-  // seven client hints are attached to the image request, and seven to the main
-  // frame request.
-  EXPECT_EQ(14u, count_client_hints_headers_seen());
+  // The user agent hint is attached to all three requests:
+  EXPECT_EQ(3u, count_user_agent_hint_headers_seen());
+
+  // Ten client hints are attached to the image request, and ten to the
+  // main frame request.
+  EXPECT_EQ(20u, count_client_hints_headers_seen());
 }
 
 // The test first fetches a page that sets Accept-CH-Lifetime. Next, it fetches
@@ -1111,7 +1154,7 @@
   content::FetchHistogramsFromChildProcesses();
   SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
 
-  histogram_tester.ExpectUniqueSample("ClientHints.UpdateSize", 7, 1);
+  histogram_tester.ExpectUniqueSample("ClientHints.UpdateSize", 11u, 1);
   // accept_ch_with_lifetime_url() sets client hints persist duration to 3600
   // seconds.
   histogram_tester.ExpectUniqueSample("ClientHints.PersistDuration",
@@ -1128,9 +1171,12 @@
   SetClientHintExpectationsOnSubresources(true);
   ui_test_utils::NavigateToURL(browser(), redirect_url());
 
-  // seven client hints are attached to the image request, and seven to the main
-  // frame request.
-  EXPECT_EQ(14u, count_client_hints_headers_seen());
+  // The user agent hint is attached to all three requests:
+  EXPECT_EQ(3u, count_user_agent_hint_headers_seen());
+
+  // Ten client hints are attached to the image request, and ten to the
+  // main frame request.
+  EXPECT_EQ(20u, count_client_hints_headers_seen());
 }
 
 // Ensure that even when cookies are blocked, client hint preferences are
@@ -1182,7 +1228,7 @@
   content::FetchHistogramsFromChildProcesses();
   SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
 
-  histogram_tester.ExpectUniqueSample("ClientHints.UpdateSize", 7, 1);
+  histogram_tester.ExpectUniqueSample("ClientHints.UpdateSize", 11u, 1);
   // |gurl_with| tries to set client hints persist duration to 3600 seconds.
   histogram_tester.ExpectUniqueSample("ClientHints.PersistDuration",
                                       3600 * 1000, 1);
@@ -1205,9 +1251,12 @@
   ui_test_utils::NavigateToURL(browser(),
                                without_accept_ch_without_lifetime_url());
 
-  // seven client hints are attached to the image request, and seven to the main
-  // frame request.
-  EXPECT_EQ(14u, count_client_hints_headers_seen());
+  // The user agent hint is attached to all three requests:
+  EXPECT_EQ(3u, count_user_agent_hint_headers_seen());
+
+  // Ten client hints are attached to the image request, and ten to the
+  // main frame request.
+  EXPECT_EQ(20u, count_client_hints_headers_seen());
 
   // Clear settings.
   HostContentSettingsMapFactory::GetForProfile(browser()->profile())
@@ -1278,8 +1327,9 @@
   histogram_tester.ExpectUniqueSample("ClientHints.UpdateEventCount", 1, 1);
   content::FetchHistogramsFromChildProcesses();
   SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
+  EXPECT_EQ(1u, count_user_agent_hint_headers_seen());
 
-  histogram_tester.ExpectUniqueSample("ClientHints.UpdateSize", 7, 1);
+  histogram_tester.ExpectUniqueSample("ClientHints.UpdateSize", 11u, 1);
   // accept_ch_with_lifetime_url() tries to set client hints persist duration to
   // 3600 seconds.
   histogram_tester.ExpectUniqueSample("ClientHints.PersistDuration",
@@ -1301,6 +1351,7 @@
                                without_accept_ch_without_lifetime_url());
   EXPECT_EQ(0u, count_client_hints_headers_seen());
   VerifyContentSettingsNotNotified();
+  EXPECT_EQ(1u, count_user_agent_hint_headers_seen());
 
   // Allow the Javascript: Client hints should now be attached.
   HostContentSettingsMapFactory::GetForProfile(browser()->profile())
@@ -1313,9 +1364,12 @@
   ui_test_utils::NavigateToURL(browser(),
                                without_accept_ch_without_lifetime_url());
 
-  // seven client hints are attached to the image request, and seven to the main
-  // frame request.
-  EXPECT_EQ(14u, count_client_hints_headers_seen());
+  // The user agent hint is attached to all three requests:
+  EXPECT_EQ(3u, count_user_agent_hint_headers_seen());
+
+  // Ten client hints are attached to the image request, and ten to the
+  // main frame request.
+  EXPECT_EQ(20u, count_client_hints_headers_seen());
 
   // Clear settings.
   HostContentSettingsMapFactory::GetForProfile(browser()->profile())
@@ -1346,6 +1400,7 @@
           CONTENT_SETTING_BLOCK);
   ui_test_utils::NavigateToURL(browser(),
                                accept_ch_without_lifetime_img_localhost());
+  EXPECT_EQ(0u, count_user_agent_hint_headers_seen());
   EXPECT_EQ(0u, count_client_hints_headers_seen());
   EXPECT_EQ(1u, third_party_request_count_seen());
   EXPECT_EQ(0u, third_party_client_hints_count_seen());
@@ -1361,9 +1416,10 @@
   ui_test_utils::NavigateToURL(browser(),
                                accept_ch_without_lifetime_img_localhost());
 
-  EXPECT_EQ(7u, count_client_hints_headers_seen());
+  EXPECT_EQ(2u, count_user_agent_hint_headers_seen());
+  EXPECT_EQ(10u, count_client_hints_headers_seen());
   EXPECT_EQ(2u, third_party_request_count_seen());
-  EXPECT_EQ(0u, third_party_client_hints_count_seen());
+  EXPECT_EQ(1u, third_party_client_hints_count_seen());
   VerifyContentSettingsNotNotified();
 
   // Clear settings.
@@ -1379,9 +1435,10 @@
           CONTENT_SETTING_BLOCK);
   ui_test_utils::NavigateToURL(browser(),
                                accept_ch_without_lifetime_img_localhost());
-  EXPECT_EQ(7u, count_client_hints_headers_seen());
+  EXPECT_EQ(2u, count_user_agent_hint_headers_seen());
+  EXPECT_EQ(10u, count_client_hints_headers_seen());
   EXPECT_EQ(3u, third_party_request_count_seen());
-  EXPECT_EQ(0u, third_party_client_hints_count_seen());
+  EXPECT_EQ(1u, third_party_client_hints_count_seen());
 
   // Clear settings.
   HostContentSettingsMapFactory::GetForProfile(browser()->profile())
@@ -1415,9 +1472,10 @@
 
   SetClientHintExpectationsOnSubresources(true);
   ui_test_utils::NavigateToURL(browser(), gurl);
-  EXPECT_EQ(7u, count_client_hints_headers_seen());
+  EXPECT_EQ(2u, count_user_agent_hint_headers_seen());
+  EXPECT_EQ(10u, count_client_hints_headers_seen());
   EXPECT_EQ(1u, third_party_request_count_seen());
-  EXPECT_EQ(0u, third_party_client_hints_count_seen());
+  EXPECT_EQ(1u, third_party_client_hints_count_seen());
 
   // Clear settings.
   HostContentSettingsMapFactory::GetForProfile(browser()->profile())
@@ -1448,7 +1506,7 @@
   content::FetchHistogramsFromChildProcesses();
   SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
 
-  histogram_tester.ExpectUniqueSample("ClientHints.UpdateSize", 7, 1);
+  histogram_tester.ExpectUniqueSample("ClientHints.UpdateSize", 11u, 1);
   // accept_ch_with_lifetime_url() sets client hints persist duration to 3600
   // seconds.
   histogram_tester.ExpectUniqueSample("ClientHints.PersistDuration",
@@ -1466,9 +1524,12 @@
   ui_test_utils::NavigateToURL(incognito,
                                without_accept_ch_without_lifetime_url());
 
-  // Seven client hints are attached to the image request, and seven to the main
-  // frame request.
-  EXPECT_EQ(14u, count_client_hints_headers_seen());
+  // The user agent hint is attached to all three requests:
+  EXPECT_EQ(3u, count_user_agent_hint_headers_seen());
+
+  // Ten client hints are attached to the image request, and ten to the
+  // main frame request.
+  EXPECT_EQ(20u, count_client_hints_headers_seen());
 
   // Navigate using regular profile. Client hints should not be send.
   SetClientHintExpectationsOnMainFrame(false);
@@ -1476,9 +1537,11 @@
   ui_test_utils::NavigateToURL(browser(),
                                without_accept_ch_without_lifetime_url());
 
-  // Seven client hints are attached to the image request, and seven to the main
-  // frame request.
-  EXPECT_EQ(14u, count_client_hints_headers_seen());
+  // The user agent hint is attached to the two new requests.
+  EXPECT_EQ(5u, count_user_agent_hint_headers_seen());
+
+  // No additional hints are sent.
+  EXPECT_EQ(20u, count_client_hints_headers_seen());
 }
 
 class ClientHintsWebHoldbackBrowserTest : public ClientHintsBrowserTest {
@@ -1491,8 +1554,7 @@
     return web_effective_connection_type_override_;
   }
 
- private:
-  void ConfigureHoldbackExperiment() {
+  std::unique_ptr<base::FeatureList> EnabledFeatures() override {
     base::FieldTrialParamAssociator::GetInstance()->ClearAllParamsForTesting();
     const std::string kTrialName = "TrialFoo";
     const std::string kGroupName = "GroupFoo";  // Value not used
@@ -1505,17 +1567,21 @@
     params["web_effective_connection_type_override"] =
         net::GetNameForEffectiveConnectionType(
             web_effective_connection_type_override_);
-    ASSERT_TRUE(
+    EXPECT_TRUE(
         base::FieldTrialParamAssociator::GetInstance()
             ->AssociateFieldTrialParams(kTrialName, kGroupName, params));
 
     std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
+    feature_list->InitializeFromCommandLine("UserAgentClientHint", "");
     feature_list->RegisterFieldTrialOverride(
         features::kNetworkQualityEstimatorWebHoldback.name,
         base::FeatureList::OVERRIDE_ENABLE_FEATURE, trial.get());
-    scoped_feature_list_override_.InitWithFeatureList(std::move(feature_list));
+    return feature_list;
   }
 
+ private:
+  void ConfigureHoldbackExperiment() {}
+
   const net::EffectiveConnectionType web_effective_connection_type_override_ =
       net::EFFECTIVE_CONNECTION_TYPE_3G;
 
@@ -1548,7 +1614,8 @@
   content::FetchHistogramsFromChildProcesses();
   SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
 
-  EXPECT_EQ(14u, count_client_hints_headers_seen());
+  EXPECT_EQ(3u, count_user_agent_hint_headers_seen());
+  EXPECT_EQ(20u, count_client_hints_headers_seen());
   EXPECT_EQ(0u, third_party_request_count_seen());
   EXPECT_EQ(0u, third_party_client_hints_count_seen());
 }
diff --git a/chrome/browser/extensions/api/extension_action/browser_action_interactive_test.cc b/chrome/browser/extensions/api/extension_action/browser_action_interactive_test.cc
index e390712..4df9767d 100644
--- a/chrome/browser/extensions/api/extension_action/browser_action_interactive_test.cc
+++ b/chrome/browser/extensions/api/extension_action/browser_action_interactive_test.cc
@@ -302,15 +302,10 @@
   test_util->HidePopup();
 }
 
-#if defined(OS_LINUX)
-#define MAYBE_TestOpenPopupDoesNotCloseOtherPopups DISABLED_TestOpenPopupDoesNotCloseOtherPopups
-#else
-#define MAYBE_TestOpenPopupDoesNotCloseOtherPopups TestOpenPopupDoesNotCloseOtherPopups
-#endif
 // Tests if there is already a popup open (by a user click or otherwise), that
 // the openPopup API does not override it.
 IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveTest,
-                       MAYBE_TestOpenPopupDoesNotCloseOtherPopups) {
+                       TestOpenPopupDoesNotCloseOtherPopups) {
   if (!ShouldRunPopupTest())
     return;
 
diff --git a/chrome/browser/extensions/api/identity/identity_get_auth_token_function.cc b/chrome/browser/extensions/api/identity/identity_get_auth_token_function.cc
index ec62ba7..cb8a9e0 100644
--- a/chrome/browser/extensions/api/identity/identity_get_auth_token_function.cc
+++ b/chrome/browser/extensions/api/identity/identity_get_auth_token_function.cc
@@ -4,14 +4,14 @@
 
 #include "chrome/browser/extensions/api/identity/identity_get_auth_token_function.h"
 
-#include <memory>
 #include <set>
-#include <string>
 #include <vector>
 
 #include "base/bind.h"
+#include "base/location.h"
 #include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/task/post_task.h"
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/extensions/api/identity/identity_api.h"
@@ -24,6 +24,8 @@
 #include "chrome/common/extensions/api/identity.h"
 #include "components/prefs/pref_service.h"
 #include "components/signin/core/browser/signin_pref_names.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
 #include "content/public/common/service_manager_connection.h"
 #include "extensions/common/extension_l10n_util.h"
 #include "google_apis/gaia/gaia_urls.h"
@@ -33,7 +35,6 @@
 
 #if defined(OS_CHROMEOS)
 #include "chrome/browser/app_mode/app_mode_utils.h"
-#include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/login/session/user_session_manager.h"
 #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
 #include "chrome/browser/chromeos/settings/device_oauth2_token_service.h"
@@ -76,7 +77,8 @@
       token_key_(/*extension_id=*/std::string(),
                  /*account_id=*/std::string(),
                  /*scopes=*/std::set<std::string>()),
-      scoped_identity_manager_observer_(this) {
+      scoped_identity_manager_observer_(this),
+      weak_ptr_factory_(this) {
 }
 
 IdentityGetAuthTokenFunction::~IdentityGetAuthTokenFunction() {
@@ -142,9 +144,12 @@
 
   if (gaia_id.empty() || IsPrimaryAccountOnly()) {
     // Try the primary account.
-    GetMojoIdentityManager()->GetPrimaryAccountInfo(base::BindOnce(
-        &IdentityGetAuthTokenFunction::OnReceivedPrimaryAccountInfo, this,
-        gaia_id));
+    // TODO(https://crbug.com/932400): collapse the asynchronicity
+    base::PostTaskWithTraits(
+        FROM_HERE, {content::BrowserThread::UI},
+        base::BindOnce(
+            &IdentityGetAuthTokenFunction::GetAuthTokenForPrimaryAccount,
+            weak_ptr_factory_.GetWeakPtr(), gaia_id));
   } else {
     // Get the AccountInfo for the account that the extension wishes to use.
     GetMojoIdentityManager()->GetAccountInfoFromGaiaId(
@@ -157,35 +162,36 @@
   return true;
 }
 
-void IdentityGetAuthTokenFunction::OnReceivedPrimaryAccountInfo(
-    const std::string& extension_gaia_id,
-    const base::Optional<AccountInfo>& account_info,
-    const ::identity::AccountState& account_state) {
-  std::string primary_gaia_id;
-  if (account_info)
-    primary_gaia_id = account_info->gaia;
-
+void IdentityGetAuthTokenFunction::GetAuthTokenForPrimaryAccount(
+    const std::string& extension_gaia_id) {
+  AccountInfo primary_account_info =
+      IdentityManagerFactory::GetForProfile(GetProfile())
+          ->GetPrimaryAccountInfo();
   bool primary_account_only = IsPrimaryAccountOnly();
 
   // Detect and handle the case where the extension is using an account other
   // than the primary account.
   if (primary_account_only && !extension_gaia_id.empty() &&
-      extension_gaia_id != primary_gaia_id) {
+      extension_gaia_id != primary_account_info.gaia) {
     // TODO(courage): should this be a different error?
     CompleteFunctionWithError(identity_constants::kUserNotSignedIn);
     return;
   }
 
-  if (primary_account_only || !primary_gaia_id.empty()) {
+  auto* identity_manager = IdentityManagerFactory::GetForProfile(GetProfile());
+  if (primary_account_only || !primary_account_info.gaia.empty()) {
     // The extension is using the primary account.
-    OnReceivedExtensionAccountInfo(primary_gaia_id, account_info,
-                                   account_state);
+    ::identity::AccountState account_state;
+    account_state.has_refresh_token =
+        identity_manager->HasAccountWithRefreshToken(
+            primary_account_info.account_id);
+    OnReceivedExtensionAccountInfo(
+        primary_account_info.gaia,
+        base::Optional<AccountInfo>(primary_account_info), account_state);
   } else {
     // No primary account, try the first account in cookies.
     DCHECK_EQ(AccountListeningMode::kNotListening, account_listening_mode_);
     account_listening_mode_ = AccountListeningMode::kListeningCookies;
-    identity::IdentityManager* identity_manager =
-        IdentityManagerFactory::GetForProfile(GetProfile());
     identity::AccountsInCookieJarInfo accounts_in_cookies =
         identity_manager->GetAccountsInCookieJar();
     if (accounts_in_cookies.accounts_are_fresh) {
diff --git a/chrome/browser/extensions/api/identity/identity_get_auth_token_function.h b/chrome/browser/extensions/api/identity/identity_get_auth_token_function.h
index b980e6d..294eb2c 100644
--- a/chrome/browser/extensions/api/identity/identity_get_auth_token_function.h
+++ b/chrome/browser/extensions/api/identity/identity_get_auth_token_function.h
@@ -6,8 +6,11 @@
 #define CHROME_BROWSER_EXTENSIONS_API_IDENTITY_IDENTITY_GET_AUTH_TOKEN_FUNCTION_H_
 
 #include <memory>
+#include <string>
 
 #include "base/callback_list.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
 #include "base/scoped_observer.h"
 #include "chrome/browser/extensions/api/identity/gaia_web_auth_flow.h"
 #include "chrome/browser/extensions/api/identity/identity_mint_queue.h"
@@ -126,15 +129,10 @@
   FRIEND_TEST_ALL_PREFIXES(GetAuthTokenFunctionTest, InteractiveQueueShutdown);
   FRIEND_TEST_ALL_PREFIXES(GetAuthTokenFunctionTest, NoninteractiveShutdown);
 
-  // Called by the IdentityManager in response to this class' request for the
-  // primary account info. Extra arguments that are bound internally at the time
-  // of calling the IdentityManager:
+  // Request the primary account info.
   // |extension_gaia_id|: The GAIA ID that was set in the parameters for this
   // instance, or empty if this was not in the parameters.
-  void OnReceivedPrimaryAccountInfo(
-      const std::string& extension_gaia_id,
-      const base::Optional<AccountInfo>& account_info,
-      const identity::AccountState& account_state);
+  void GetAuthTokenForPrimaryAccount(const std::string& extension_gaia_id);
 
   // Called when the AccountInfo that this instance should use is available.
   void OnReceivedExtensionAccountInfo(
@@ -242,6 +240,8 @@
   };
   AccountListeningMode account_listening_mode_ =
       AccountListeningMode::kNotListening;
+
+  base::WeakPtrFactory<IdentityGetAuthTokenFunction> weak_ptr_factory_;
 };
 
 }  // namespace extensions
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 6bfbcb15..45cb427 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1194,6 +1194,11 @@
     "expiry_milestone": -1
   },
   {
+    "name": "enable-filesystem-in-incognito",
+    "owners": [ "rhalavati" ],
+    "expiry_milestone": 76
+  },
+  {
     "name": "enable-first-run-ui-transitions",
     // "owners": [ "your-team" ],
     "expiry_milestone": 76
@@ -2213,11 +2218,6 @@
     "expiry_milestone": 76
   },
   {
-    "name": "gesture-editing",
-    // "owners": [ "your-team" ],
-    "expiry_milestone": 76
-  },
-  {
     "name": "gesture-typing",
     // "owners": [ "your-team" ],
     "expiry_milestone": 76
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 2fb4cf6..d2853aa 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -523,6 +523,10 @@
     "Enable support for specifying a target element using a css selector in "
     "the fragment identifier.";
 
+const char kEnableFilesystemInIncognitoName[] = "Filesystem API in Incognito";
+const char kEnableFilesystemInIncognitoDescription[] =
+    "Enable Filesystem API in incognito mode.";
+
 const char kEnableNoScriptPreviewsName[] = "NoScript previews";
 
 const char kEnableNoScriptPreviewsDescription[] =
@@ -3350,11 +3354,6 @@
     "Forces display of the stylus tools menu in the shelf and the stylus "
     "section in settings, even if there is no attached stylus device.";
 
-const char kGestureEditingName[] = "Gesture editing for the virtual keyboard.";
-const char kGestureEditingDescription[] =
-    "Enable/Disable gesture editing option in the settings page for the "
-    "virtual keyboard.";
-
 const char kGestureTypingName[] = "Gesture typing for the virtual keyboard.";
 const char kGestureTypingDescription[] =
     "Enable/Disable gesture typing option in the settings page for the virtual "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index dc8b9023a..2587f718 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -345,6 +345,9 @@
 extern const char kEnableCSSFragmentIdentifiersName[];
 extern const char kEnableCSSFragmentIdentifiersDescription[];
 
+extern const char kEnableFilesystemInIncognitoName[];
+extern const char kEnableFilesystemInIncognitoDescription[];
+
 extern const char kEnableNoScriptPreviewsName[];
 extern const char kEnableNoScriptPreviewsDescription[];
 
@@ -2012,9 +2015,6 @@
 extern const char kForceEnableStylusToolsName[];
 extern const char kForceEnableStylusToolsDescription[];
 
-extern const char kGestureEditingName[];
-extern const char kGestureEditingDescription[];
-
 extern const char kGestureTypingName[];
 extern const char kGestureTypingDescription[];
 
diff --git a/chrome/browser/picture_in_picture/DEPS b/chrome/browser/picture_in_picture/DEPS
index 169b1b5..0217078 100644
--- a/chrome/browser/picture_in_picture/DEPS
+++ b/chrome/browser/picture_in_picture/DEPS
@@ -2,9 +2,9 @@
   "picture_in_picture_window_controller_browsertest\.cc": [
     "+ash/accelerators/accelerator_controller.h",
     "+ash/shell.h",
-    "+chrome/browser/ui/views/overlay/next_track_image_button.h",
     "+chrome/browser/ui/views/overlay/overlay_window_views.h",
     "+chrome/browser/ui/views/overlay/playback_image_button.h",
     "+chrome/browser/ui/views/overlay/skip_ad_label_button.h",
+    "+chrome/browser/ui/views/overlay/track_image_button.h",
   ],
 }
diff --git a/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc b/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc
index 1e504057..a91d698 100644
--- a/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc
+++ b/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc
@@ -44,10 +44,10 @@
 #include "ui/gfx/codec/png_codec.h"
 
 #if !defined(OS_ANDROID)
-#include "chrome/browser/ui/views/overlay/next_track_image_button.h"
 #include "chrome/browser/ui/views/overlay/overlay_window_views.h"
 #include "chrome/browser/ui/views/overlay/playback_image_button.h"
 #include "chrome/browser/ui/views/overlay/skip_ad_label_button.h"
+#include "chrome/browser/ui/views/overlay/track_image_button.h"
 #include "ui/views/controls/button/image_button.h"
 #include "ui/views/widget/widget_observer.h"
 #endif
@@ -90,6 +90,7 @@
   MOCK_METHOD1(SetAlwaysHidePlayPauseButton, void(bool));
   MOCK_METHOD0(SkipAd, void());
   MOCK_METHOD0(NextTrack, void());
+  MOCK_METHOD0(PreviousTrack, void());
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MockPictureInPictureWindowController);
@@ -2072,6 +2073,52 @@
       overlay_window->next_track_controls_view_for_testing()->IsDrawn());
 }
 
+// Tests that a Previous Track button is displayed in the Picture-in-Picture
+// window when Media Session Action "previoustrack" is handled by the website.
+IN_PROC_BROWSER_TEST_F(MediaSessionPictureInPictureWindowControllerBrowserTest,
+                       PreviousTrackButtonVisibility) {
+  LoadTabAndEnterPictureInPicture(browser());
+  OverlayWindowViews* overlay_window = static_cast<OverlayWindowViews*>(
+      window_controller()->GetWindowForTesting());
+  ASSERT_TRUE(overlay_window);
+
+  // Previous Track button is not displayed initially when mouse is hovering
+  // over the window.
+  MoveMouseOver(overlay_window);
+  EXPECT_FALSE(
+      overlay_window->previous_track_controls_view_for_testing()->IsDrawn());
+
+  content::WebContents* active_web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+
+  // Previous Track button is not displayed if video is not playing even if
+  // mouse is hovering over the window and media session action handler has been
+  // set.
+  ASSERT_TRUE(content::ExecuteScript(
+      active_web_contents, "setMediaSessionActionHandler('previoustrack');"));
+  base::RunLoop().RunUntilIdle();
+  MoveMouseOver(overlay_window);
+  EXPECT_FALSE(
+      overlay_window->previous_track_controls_view_for_testing()->IsDrawn());
+
+  // Play video and check that Previous Track button is now displayed when
+  // video plays and mouse is hovering over the window.
+  ASSERT_TRUE(content::ExecuteScript(active_web_contents, "video.play();"));
+  base::RunLoop().RunUntilIdle();
+  MoveMouseOver(overlay_window);
+  EXPECT_TRUE(
+      overlay_window->previous_track_controls_view_for_testing()->IsDrawn());
+
+  // Unset action handler and check that Previous Track button is not displayed
+  // when video plays and mouse is hovering over the window.
+  ASSERT_TRUE(content::ExecuteScript(
+      active_web_contents, "unsetMediaSessionActionHandler('previoustrack');"));
+  base::RunLoop().RunUntilIdle();
+  MoveMouseOver(overlay_window);
+  EXPECT_FALSE(
+      overlay_window->previous_track_controls_view_for_testing()->IsDrawn());
+}
+
 // Tests that clicking the Skip Ad button in the Picture-in-Picture window
 // calls the Media Session Action "skipad" handler function.
 IN_PROC_BROWSER_TEST_F(MediaSessionPictureInPictureWindowControllerBrowserTest,
@@ -2146,6 +2193,27 @@
                 .WaitAndGetTitle());
 }
 
+// Tests that clicking the Previous Track button in the Picture-in-Picture
+// window calls the Media Session Action "previoustrack" handler function.
+IN_PROC_BROWSER_TEST_F(MediaSessionPictureInPictureWindowControllerBrowserTest,
+                       PreviousTrackHandlerCalled) {
+  LoadTabAndEnterPictureInPicture(browser());
+  content::WebContents* active_web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  ASSERT_TRUE(content::ExecuteScript(active_web_contents, "video.play();"));
+  ASSERT_TRUE(content::ExecuteScript(
+      active_web_contents, "setMediaSessionActionHandler('previoustrack');"));
+  base::RunLoop().RunUntilIdle();
+
+  // Simulates user clicking "Previous Track" and check the handler function is
+  // called.
+  window_controller()->PreviousTrack();
+  base::string16 expected_title = base::ASCIIToUTF16("previoustrack");
+  EXPECT_EQ(expected_title,
+            content::TitleWatcher(active_web_contents, expected_title)
+                .WaitAndGetTitle());
+}
+
 // Show/hide page and check that Auto Picture-in-Picture is not triggered. This
 // test is most likely going to be flaky the day the tested thing fails.
 // Do NOT disable test. Ping /chrome/browser/picture_in_picture/OWNERS instead.
diff --git a/chrome/browser/profiles/profile_manager.cc b/chrome/browser/profiles/profile_manager.cc
index 9ab37ce..a8a11c5 100644
--- a/chrome/browser/profiles/profile_manager.cc
+++ b/chrome/browser/profiles/profile_manager.cc
@@ -1654,7 +1654,7 @@
 
   identity::IdentityManager* identity_manager =
       IdentityManagerFactory::GetForProfile(profile);
-  AccountInfo account_info = identity_manager->GetPrimaryAccountInfo();
+  CoreAccountInfo account_info = identity_manager->GetPrimaryAccountInfo();
   base::string16 username = base::UTF8ToUTF16(account_info.email);
 
   ProfileAttributesStorage& storage = GetProfileAttributesStorage();
diff --git a/chrome/browser/profiles/profiles_state.cc b/chrome/browser/profiles/profiles_state.cc
index e0201f1..9642a8e4 100644
--- a/chrome/browser/profiles/profiles_state.cc
+++ b/chrome/browser/profiles/profiles_state.cc
@@ -153,7 +153,7 @@
   // The vector returned by GetAccountsWithRefreshTokens() contains
   // the primary account too, so we need to remove it from the list.
   DCHECK(identity_manager->HasPrimaryAccount());
-  AccountInfo primary_account = identity_manager->GetPrimaryAccountInfo();
+  CoreAccountInfo primary_account = identity_manager->GetPrimaryAccountInfo();
 
   auto primary_index = std::find_if(
       accounts.begin(), accounts.end(),
diff --git a/chrome/browser/renderer_preferences_util.cc b/chrome/browser/renderer_preferences_util.cc
index f3fc761c..f5ed7f0 100644
--- a/chrome/browser/renderer_preferences_util.cc
+++ b/chrome/browser/renderer_preferences_util.cc
@@ -14,7 +14,7 @@
 #include "chrome/common/pref_names.h"
 #include "components/language/core/browser/pref_names.h"
 #include "components/prefs/pref_service.h"
-#include "content/public/common/renderer_preferences_util.h"
+#include "content/public/browser/renderer_preferences_util.h"
 #include "content/public/common/webrtc_ip_handling_policy.h"
 #include "media/media_buildflags.h"
 #include "third_party/blink/public/mojom/renderer_preferences.mojom.h"
@@ -150,6 +150,7 @@
 
 #if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_WIN)
   content::UpdateFontRendererPreferencesFromSystemSettings(prefs);
+  content::UpdateFocusRingPreferencesFromSystemSettings(prefs);
 #endif
 
 #if !defined(OS_MACOSX)
diff --git a/chrome/browser/resources/app_management/BUILD.gn b/chrome/browser/resources/app_management/BUILD.gn
index 09e156c..3573cff 100644
--- a/chrome/browser/resources/app_management/BUILD.gn
+++ b/chrome/browser/resources/app_management/BUILD.gn
@@ -17,6 +17,7 @@
       ":chrome_app_permission_view",
       ":constants",
       ":dom_switch",
+      ":expandable_app_list",
       ":fake_page_handler",
       ":main_view",
       ":metadata_view",
@@ -112,6 +113,13 @@
   js_library("dom_switch") {
   }
 
+  js_library("expandable_app_list") {
+    deps = [
+      ":app_item",
+      ":store_client",
+    ]
+  }
+
   js_library("fake_page_handler") {
     deps = [
       ":constants",
diff --git a/chrome/browser/resources/app_management/expandable_app_list.html b/chrome/browser/resources/app_management/expandable_app_list.html
new file mode 100644
index 0000000..d1add13a
--- /dev/null
+++ b/chrome/browser/resources/app_management/expandable_app_list.html
@@ -0,0 +1,85 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+
+<link rel="import" href="app_item.html">
+<link rel="import" href="shared_style.html">
+<link rel="import" href="store_client.html">
+<link rel="import" href="permission_toggle.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/iron-collapse/iron-collapse.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button-light.html">
+
+<dom-module id="app-management-expandable-app-list">
+  <template>
+    <style include="app-management-shared-css">
+      .app-management-item-arrow {
+        margin-inline-end: 8px;
+        padding: 12px;
+      }
+
+      #app-list-title {
+        padding: 16px 24px;
+      }
+
+      app-management-permission-toggle {
+        margin-inline-end: 24px;
+      }
+    </style>
+    <!-- TODO(ceciliani) Avoid using dom-if, and use slot by getting |items|
+    from dom-repeat -->
+    <!-- TODO(calamity) Make a more generic polymer element for expandable
+    list. -->
+    <div class="card-container">
+      <div id="app-list-title" class="header-text">[[listTitle]]</div>
+      <template is="dom-repeat" items="[[displayedApps]]">
+        <template is="dom-if" if="[[!notificationsViewSelected_()]]">
+          <app-management-app-item app="[[item]]">
+            <paper-icon-button-light slot="right-content"
+                class="subpage-arrow app-management-item-arrow" actionable>
+              <button></button>
+            </paper-icon-button-light>
+          </app-management-app-item>
+        </template>
+        <template is="dom-if" if="[[notificationsViewSelected_()]]">
+          <app-management-app-item app="[[item]]">
+            <app-management-permission-toggle slot="right-content"
+                app="[[item]]"
+                permission-type="CONTENT_SETTINGS_TYPE_NOTIFICATIONS">
+            </app-management-permission-toggle>
+          </app-management-app-item>
+        </template>
+      </template>
+
+      <iron-collapse opened="[[listExpanded_]]">
+        <template is="dom-repeat" items="[[collapsedApps]]">
+          <template is="dom-if" if="[[!notificationsViewSelected_()]]">
+            <app-management-app-item app="[[item]]">
+              <paper-icon-button-light slot="right-content"
+                  class="subpage-arrow app-management-item-arrow" actionable>
+                <button></button>
+              </paper-icon-button-light>
+            </app-management-app-item>
+          </template>
+          <template is="dom-if" if="[[notificationsViewSelected_()]]">
+            <app-management-app-item app="[[item]]">
+              <app-management-permission-toggle slot="right-content"
+                  app="[[item]]"
+                  permission-type="CONTENT_SETTINGS_TYPE_NOTIFICATIONS">
+              </app-management-permission-toggle>
+            </app-management-app-item>
+          </template>
+        </template>
+      </iron-collapse>
+
+      <div id="expander-row" class="expander-list-row"
+          on-click="toggleListExpanded_">
+        <span>[[moreAppsString_(collapsedApps.length,listExpanded_)]]</span>
+        <paper-icon-button-light class="expand-button">
+          <button>
+            <iron-icon icon="[[getCollapsedIcon_(listExpanded_)]]">
+            </iron-icon>
+          </button>
+        </paper-icon-button-light>
+      </div>
+    </div>
+  </template>
+  <script src="expandable_app_list.js"></script>
+</dom-module>
diff --git a/chrome/browser/resources/app_management/expandable_app_list.js b/chrome/browser/resources/app_management/expandable_app_list.js
new file mode 100644
index 0000000..b4dc9aa
--- /dev/null
+++ b/chrome/browser/resources/app_management/expandable_app_list.js
@@ -0,0 +1,101 @@
+// 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.
+
+Polymer({
+  is: 'app-management-expandable-app-list',
+
+  behaviors: [
+    app_management.StoreClient,
+  ],
+
+  properties: {
+    /**
+     * @private {Page}
+     */
+    currentPage_: {
+      type: Object,
+      observer: 'onViewChanged_',
+    },
+
+    /**
+     * List of apps displayed before expanding the app list.
+     * @type {Array<App>}
+     */
+    displayedApps: Array,
+
+    /**
+     * Title of the expandable list.
+     * @type {String}
+     */
+    listTitle: String,
+
+    /**
+     * List of apps displayed after expanding app list.
+     * @type {Array<App>}
+     */
+    collapsedApps: {
+      type: Array,
+      observer: 'onAppsChanged_',
+    },
+
+    /**
+     * @private {boolean}
+     */
+    listExpanded_: Boolean,
+  },
+
+  attached: function() {
+    this.watch('currentPage_', state => state.currentPage);
+
+    this.updateFromStore();
+  },
+
+  /**
+   * @private
+   */
+  onAppsChanged_: function() {
+    this.$['expander-row'].hidden = this.collapsedApps.length === 0;
+  },
+
+  /**
+   * Collapse the list when changing a page if it is open so that list is always
+   * collapsed when entering the page.
+   * @private
+   */
+  onViewChanged_: function() {
+    this.$['app-list-title'].hidden = !this.listTitle;
+    this.listExpanded_ = false;
+  },
+
+  /**
+   * @private
+   */
+  toggleListExpanded_: function() {
+    this.listExpanded_ = !this.listExpanded_;
+  },
+
+  /**
+   * @param {boolean} listExpanded
+   * @return {string}
+   * @private
+   */
+  getCollapsedIcon_: function(listExpanded) {
+    return listExpanded ? 'cr:expand-less' : 'cr:expand-more';
+  },
+
+  /**
+   * @param {number} numApps
+   * @param {boolean} listExpanded
+   * @return {string}
+   * @private
+   */
+  moreAppsString_: function(numApps, listExpanded) {
+    return listExpanded ? loadTimeData.getString('lessApps') :
+                          loadTimeData.getStringF('moreApps', numApps);
+  },
+
+  notificationsViewSelected_: function() {
+    return this.currentPage_.pageType === PageType.NOTIFICATIONS;
+  },
+});
diff --git a/chrome/browser/resources/app_management/main_view.html b/chrome/browser/resources/app_management/main_view.html
index d00cae4..0492fb0 100644
--- a/chrome/browser/resources/app_management/main_view.html
+++ b/chrome/browser/resources/app_management/main_view.html
@@ -1,6 +1,6 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
-<link rel="import" href="app_item.html">
+<link rel="import" href="expandable_app_list.html">
 <link rel="import" href="shared_style.html">
 <link rel="import" href="store_client.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-collapse/iron-collapse.html">
@@ -9,27 +9,6 @@
 <dom-module id="app-management-main-view">
   <template>
     <style include="app-management-shared-css">
-      #app-list-title {
-        padding: 16px 24px;
-      }
-
-      #expand-button {
-        height: 36px;
-        margin-inline-end: 12px;
-        width: 36px;
-      }
-
-      #expander-row {
-        align-items: center;
-        border-top: var(--card-separator);
-        color: var(--secondary-text-color);
-        display: flex;
-        height: 50px;
-        justify-content: space-between;
-        padding-inline-end: 8px;
-        padding-inline-start: 24px;
-      }
-
       .notification-row-sublabel {
         display: flex;
         flex-direction: column;
@@ -59,46 +38,12 @@
         justify-content: space-between;
         padding: 0 24px;
       }
-
-      .app-management-item-arrow {
-        margin-inline-end: 8px;
-        padding: 12px;
-      }
     </style>
-
-    <div class="card-container">
-      <div id="app-list-title" class="header-text">$i18n{appListTitle}</div>
-      <template is="dom-repeat" items="[[displayedApps_]]">
-        <app-management-app-item app="[[item]]">
-          <paper-icon-button-light slot="right-content"
-              class="subpage-arrow app-management-item-arrow" actionable>
-            <button></button>
-          </paper-icon-button-light>
-        </app-management-app-item>
-      </template>
-
-      <iron-collapse opened="[[listExpanded_]]">
-        <template is="dom-repeat" items="[[collapsedApps_]]">
-          <app-management-app-item app="[[item]]">
-            <paper-icon-button-light slot="right-content"
-                class="subpage-arrow app-management-item-arrow" actionable>
-              <button></button>
-            </paper-icon-button-light>
-          </app-management-app-item>
-        </template>
-      </iron-collapse>
-
-      <div id="expander-row" class="expander-list-row"
-          on-click="toggleListExpanded_">
-        <span>[[moreAppsString_(apps_, listExpanded_)]]</span>
-        <paper-icon-button-light class="expand-button">
-          <button>
-            <iron-icon icon="[[getCollapsedIcon_(listExpanded_)]]">
-            </iron-icon>
-          </button>
-        </paper-icon-button-light>
-      </div>
-    </div>
+    <app-management-expandable-app-list
+        displayed-apps="[[displayedApps_]]"
+        collapsed-apps="[[collapsedApps_]]"
+        list-title="$i18n{appListTitle}">
+    </app-management-expandable-app-list>
 
     <div class="card-container">
       <span class="notification-row" on-click="onClickNotificationSublabel_">
diff --git a/chrome/browser/resources/app_management/main_view.js b/chrome/browser/resources/app_management/main_view.js
index f84e00a..82b87487 100644
--- a/chrome/browser/resources/app_management/main_view.js
+++ b/chrome/browser/resources/app_management/main_view.js
@@ -37,14 +37,6 @@
     },
 
     /**
-     * @private {boolean}
-     */
-    listExpanded_: {
-      type: Boolean,
-      value: false,
-    },
-
-    /**
      * A set containing the ids of all the apps with notifications enabled.
      * @private {!Set<string>}
      */
@@ -61,52 +53,15 @@
   },
 
   /**
-   * @param {AppMap} apps
-   * @param {boolean} listExpanded
-   * @return {?string}
-   * @private
-   */
-  moreAppsString_: function(apps, listExpanded) {
-    if (apps === undefined || listExpanded === undefined) {
-      return null;
-    }
-
-    const numApps = Object.keys(apps).length;
-    return listExpanded ?
-        loadTimeData.getString('lessApps') :
-        loadTimeData.getStringF(
-            'moreApps', numApps - NUMBER_OF_APPS_DISPLAYED_DEFAULT);
-  },
-
-  /**
-   * @private
-   */
-  toggleListExpanded_: function() {
-    this.listExpanded_ = !this.listExpanded_;
-    this.onAppsChanged_();
-  },
-
-  /**
    * @private
    */
   onAppsChanged_: function() {
     const appList = Object.values(this.apps_);
-    this.$['expander-row'].hidden =
-        appList.length <= NUMBER_OF_APPS_DISPLAYED_DEFAULT;
     this.displayedApps_ = appList.slice(0, NUMBER_OF_APPS_DISPLAYED_DEFAULT);
     this.collapsedApps_ =
         appList.slice(NUMBER_OF_APPS_DISPLAYED_DEFAULT, appList.length);
   },
 
-  /**
-   * @param {boolean} listExpanded
-   * @return {string}
-   * @private
-   */
-  getCollapsedIcon_: function(listExpanded) {
-    return listExpanded ? 'cr:expand-less' : 'cr:expand-more';
-  },
-
   /** @private */
   onClickNotificationSublabel_: function() {
     this.dispatch(app_management.actions.changePage(PageType.NOTIFICATIONS));
diff --git a/chrome/browser/resources/app_management/notifications_view.html b/chrome/browser/resources/app_management/notifications_view.html
index db3a8f2..d3f8d96 100644
--- a/chrome/browser/resources/app_management/notifications_view.html
+++ b/chrome/browser/resources/app_management/notifications_view.html
@@ -1,20 +1,13 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
-<link rel="import" href="app_item.html">
+<link rel="import" href="expandable_app_list.html">
 <link rel="import" href="shared_style.html">
 <link rel="import" href="store_client.html">
-<link rel="import" href="permission_toggle.html">
-<link rel="import" href="chrome://resources/html/icon.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-collapse/iron-collapse.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button-light.html">
 
 <dom-module id="app-management-notifications-view">
   <template>
     <style include="app-management-shared-css">
-      app-management-permission-toggle {
-        margin-inline-end: 24px;
-      }
-
       paper-icon-button-light {
         margin-inline-start: 0;
       }
@@ -30,8 +23,6 @@
         padding-inline-start: 12px;
       }
     </style>
-    <!-- TODO(ceciliani) Make this view a separate element to avoid duplicate
-    code with main view -->
     <!-- TODO(crbug.com/906508): Implement display when there is no apps at
     all  -->
     <div id="notification-view-header">
@@ -43,39 +34,10 @@
       </paper-icon-button-light>
       <div id="notification-title" class="page-title">$i18n{notifications}</div>
     </div>
-
-    <div class="card-container">
-      <template is="dom-repeat" items="[[displayedApps_]]">
-        <app-management-app-item app="[[item]]">
-          <app-management-permission-toggle slot="right-content"
-              app="[[item]]"
-              permission-type="[[notificationsPermissionType_(item)]]">
-          </app-management-permission-toggle>
-        </app-management-app-item>
-      </template>
-
-      <iron-collapse opened="[[listExpanded_]]">
-        <template is="dom-repeat" items="[[collapsedApps_]]">
-          <app-management-app-item app="[[item]]">
-            <app-management-permission-toggle slot="right-content"
-                app="[[item]]"
-                permission-type="[[notificationsPermissionType_(item)]]">
-            </app-management-permission-toggle>
-          </app-management-app-item>
-        </template>
-      </iron-collapse>
-
-      <div id="expander-row" class="expander-list-row"
-          on-click="toggleListExpanded_">
-        <span>[[moreAppsString_(listExpanded_, collapsedApps_)]]</span>
-        <paper-icon-button-light class="expand-button">
-          <button>
-            <iron-icon icon="[[getCollapsedIcon_(listExpanded_)]]">
-            </iron-icon>
-          </button>
-        </paper-icon-button-light>
-      </div>
-    </div>
+    <app-management-expandable-app-list
+        displayed-apps="[[displayedApps_]]"
+        collapsed-apps="[[collapsedApps_]]">
+    </app-management-expandable-app-list>
   </template>
  <script src="notifications_view.js"></script>
 </dom-module>
diff --git a/chrome/browser/resources/app_management/notifications_view.js b/chrome/browser/resources/app_management/notifications_view.js
index 2220170..2252b4c0 100644
--- a/chrome/browser/resources/app_management/notifications_view.js
+++ b/chrome/browser/resources/app_management/notifications_view.js
@@ -75,7 +75,6 @@
       this.displayedApps_ = this.collapsedApps_;
       this.collapsedApps_ = [];
     }
-    this.$['expander-row'].hidden = this.collapsedApps_.length === 0;
   },
 
   /**
@@ -128,25 +127,6 @@
     return newApps;
   },
 
-  /**
-   * @param {boolean} listExpanded
-   * @param {Array<App>} collapsedApps
-   * @return {string}
-   * @private
-   */
-  moreAppsString_: function(listExpanded, collapsedApps) {
-    return listExpanded ?
-        loadTimeData.getString('lessApps') :
-        loadTimeData.getStringF('moreApps', collapsedApps.length);
-  },
-
-  /**
-   * @private
-   */
-  toggleListExpanded_: function() {
-    this.listExpanded_ = !this.listExpanded_;
-  },
-
   /** @private */
   onClickBackButton_: function() {
     this.listExpanded_ = false;
@@ -158,15 +138,6 @@
   },
 
   /**
-   * @param {boolean} listExpanded
-   * @return {string}
-   * @private
-   */
-  getCollapsedIcon_: function(listExpanded) {
-    return listExpanded ? 'cr:expand-less' : 'cr:expand-more';
-  },
-
-  /**
    * Returns a boolean representation of the permission value, which used to
    * determine the position of the permission toggle.
    * @param {App} app
diff --git a/chrome/browser/resources/app_management/shared_style.html b/chrome/browser/resources/app_management/shared_style.html
index 9193bb4..9cb1aaa 100644
--- a/chrome/browser/resources/app_management/shared_style.html
+++ b/chrome/browser/resources/app_management/shared_style.html
@@ -25,6 +25,7 @@
 
       .permission-card-row {
         border-top: var(--card-separator);
+        cursor: pointer;
         padding: 0 24px;
       }
 
diff --git a/chrome/browser/resources/chromeos/login/accessibility_menu.css b/chrome/browser/resources/chromeos/login/accessibility_menu.css
deleted file mode 100644
index 1936c78..0000000
--- a/chrome/browser/resources/chromeos/login/accessibility_menu.css
+++ /dev/null
@@ -1,42 +0,0 @@
-/* Copyright (c) 2012 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. */
-
-#accessibility-menu {
-  padding-bottom: 25px;
-  padding-inline-end: 80px;
-  padding-inline-start: 25px;
-  padding-top: 15px;
-  z-index: 1;
-}
-
-.checkboxrow {
-  display: -webkit-box;
-  margin-top: 10px;
-}
-
-.checkboxlabel {
-  margin-inline-start: 10px;
-}
-
-.close-button {
-  background: url(chrome://theme/IDR_CLOSE_DIALOG) center no-repeat;
-  height: 14px;
-  position: absolute;
-  right: 7px;
-  top: 7px;
-  width: 14px;
-}
-
-.close-button:hover {
-  background-image: url(chrome://theme/IDR_CLOSE_DIALOG_H);
-}
-
-.close-button:active {
-  background-image: url(chrome://theme/IDR_CLOSE_DIALOG_P);
-}
-
-html[dir=rtl] .close-button {
-  left: 10px;
-  right: auto;
-}
diff --git a/chrome/browser/resources/chromeos/login/accessibility_menu.html b/chrome/browser/resources/chromeos/login/accessibility_menu.html
deleted file mode 100644
index 86c50e87..0000000
--- a/chrome/browser/resources/chromeos/login/accessibility_menu.html
+++ /dev/null
@@ -1,47 +0,0 @@
-<div id="accessibility-menu" class="bubble faded" hidden>
-  <div class="checkboxrow">
-    <input id="spoken-feedback" type="checkbox">
-    <label for="spoken-feedback" class="checkboxlabel"
-        i18n-content="spokenFeedbackOption">
-    </label>
-  </div>
-  <div class="checkboxrow">
-    <input id="large-cursor" type="checkbox">
-    <label for="large-cursor" class="checkboxlabel"
-        i18n-content="largeCursorOption">
-    </label>
-  </div>
-  <div class="checkboxrow">
-    <input id="high-contrast" type="checkbox">
-    <label for="high-contrast" class="checkboxlabel"
-        i18n-content="highContrastOption">
-    </label>
-  </div>
-  <div class="checkboxrow">
-    <input id="screen-magnifier" type="checkbox">
-    <label for="screen-magnifier" class="checkboxlabel"
-        i18n-content="screenMagnifierOption">
-    </label>
-  </div>
-  <div class="checkboxrow" id="select-to-speak-row">
-    <input id="select-to-speak" type="checkbox">
-    <label for="select-to-speak" class="checkboxlabel"
-        i18n-content="selectToSpeakOption">
-    </label>
-  </div>
-  <div class="checkboxrow" id="docked-magnifier-row">
-    <input id="docked-magnifier" type="checkbox">
-    <label for="docked-magnifier" class="checkboxlabel"
-        i18n-content="dockedMagnifierOption">
-    </label>
-  </div>
-  <div class="checkboxrow">
-    <input id="virtual-keyboard" type="checkbox">
-    <label for="virtual-keyboard" class="checkboxlabel"
-        i18n-content="virtualKeyboardOption">
-    </label>
-  </div>
-  <div id="close-accessibility-menu" class="close-button"
-       i18n-values="title:closeAccessibilityMenu" tabindex="0">
-  </div>
-</div>
diff --git a/chrome/browser/resources/chromeos/login/cr_ui.js b/chrome/browser/resources/chromeos/login/cr_ui.js
index f5eb50b..a143d1d 100644
--- a/chrome/browser/resources/chromeos/login/cr_ui.js
+++ b/chrome/browser/resources/chromeos/login/cr_ui.js
@@ -187,12 +187,9 @@
   };
 
   /**
-   * Clears error bubble as well as optional menus that could be open.
+   * Clears error bubble.
    */
   Oobe.clearErrors = function() {
-    var accessibilityMenu = $('accessibility-menu');
-    if (accessibilityMenu)
-      accessibilityMenu.hide();
     DisplayManager.clearErrors();
   };
 
diff --git a/chrome/browser/resources/chromeos/login/custom_elements_login.html b/chrome/browser/resources/chromeos/login/custom_elements_login.html
index 4f39445..2192ac3 100644
--- a/chrome/browser/resources/chromeos/login/custom_elements_login.html
+++ b/chrome/browser/resources/chromeos/login/custom_elements_login.html
@@ -5,7 +5,6 @@
 <include src="gaia_input.html">
 <include src="gaia_password_changed.html">
 <include src="hd-iron-icon.html">
-<include src="html-echo.html">
 <include src="offline_gaia.html">
 <include src="saml_confirm_password.html">
 <include src="saml_interstitial.html">
@@ -21,12 +20,10 @@
 <include src="oobe_buttons.html">
 <include src="oobe_change_picture.html">
 <include src="oobe_dialog.html">
-<include src="oobe_enrollment.html">
 <include src="oobe_reset.html">
 <include src="oobe_reset_confirmation_overlay.html">
 <include src="oobe_supervision_transition.html">
 <include src="encryption_migration.html">
-<include src="enrollment_license_card.html">
 <include src="sync_consent.html">
 <include src="fingerprint_setup.html">
 <include src="recommend_apps.html">
diff --git a/chrome/browser/resources/chromeos/login/custom_elements_login.js b/chrome/browser/resources/chromeos/login/custom_elements_login.js
index 146da25f..5bacf38 100644
--- a/chrome/browser/resources/chromeos/login/custom_elements_login.js
+++ b/chrome/browser/resources/chromeos/login/custom_elements_login.js
@@ -10,7 +10,6 @@
 // <include src="gaia_input.js">
 // <include src="gaia_password_changed.js">
 // <include src="hd-iron-icon.js">
-// <include src="html-echo.js">
 // <include src="offline_gaia.js">
 // <include src="saml_confirm_password.js">
 // <include src="saml_interstitial.js">
@@ -25,13 +24,11 @@
 // <include src="oobe_buttons.js">
 // <include src="oobe_change_picture.js">
 // <include src="oobe_dialog.js">
-// <include src="oobe_enrollment.js">
 // <include src="arc_terms_of_service.js">
 // <include src="oobe_reset.js">
 // <include src="oobe_reset_confirmation_overlay.js">
 // <include src="encryption_migration.js">
 // <include src="oobe_supervision_transition.js">
-// <include src="enrollment_license_card.js">
 // <include src="sync_consent.js">
 // <include src="fingerprint_setup.js">
 // <include src="recommend_apps.js">
diff --git a/chrome/browser/resources/chromeos/login/custom_elements_oobe.html b/chrome/browser/resources/chromeos/login/custom_elements_oobe.html
index 3e206d96..7a171df1 100644
--- a/chrome/browser/resources/chromeos/login/custom_elements_oobe.html
+++ b/chrome/browser/resources/chromeos/login/custom_elements_oobe.html
@@ -5,7 +5,6 @@
 <include src="gaia_input.html">
 <include src="gaia_password_changed.html">
 <include src="hd-iron-icon.html">
-<include src="html-echo.html">
 <include src="network_select_login.html">
 <include src="notification_card.html">
 <include src="offline_gaia.html">
diff --git a/chrome/browser/resources/chromeos/login/custom_elements_oobe.js b/chrome/browser/resources/chromeos/login/custom_elements_oobe.js
index 8d15efb..616e50c6 100644
--- a/chrome/browser/resources/chromeos/login/custom_elements_oobe.js
+++ b/chrome/browser/resources/chromeos/login/custom_elements_oobe.js
@@ -17,7 +17,6 @@
 // <include src="gaia_input.js">
 // <include src="gaia_password_changed.js">
 // <include src="hd-iron-icon.js">
-// <include src="html-echo.js">
 // <include src="network_select_login.js">
 // <include src="notification_card.js">
 // <include src="offline_gaia.js">
diff --git a/chrome/browser/resources/chromeos/login/md_login.html b/chrome/browser/resources/chromeos/login/md_login.html
index d73973df..9ae8e23 100644
--- a/chrome/browser/resources/chromeos/login/md_login.html
+++ b/chrome/browser/resources/chromeos/login/md_login.html
@@ -27,8 +27,8 @@
 <link rel="stylesheet" href="oobe_popup_overlay.css">
 <link rel="stylesheet" href="oobe_screen.css">
 <link rel="stylesheet" href="../../../../../ui/login/md_screen_container.css">
-<link rel="stylesheet" href="../../../../../ui/login/account_picker/md_screen_account_picker.css">
-<link rel="stylesheet" href="../../../../../ui/login/account_picker/md_user_pod_row.css">
+<link rel="stylesheet" href="../../../../../ui/login/account_picker/chromeos_screen_account_picker.css">
+<link rel="stylesheet" href="../../../../../ui/login/account_picker/chromeos_user_pod_row.css">
 <script src="chrome://resources/js/cr.js"></script>
 <script src="chrome://resources/js/event_tracker.js"></script>
 <script src="chrome://resources/js/cr/event_target.js"></script>
diff --git a/chrome/browser/resources/chromeos/login/md_login.js b/chrome/browser/resources/chromeos/login/md_login.js
index 4132607..bf1f578 100644
--- a/chrome/browser/resources/chromeos/login/md_login.js
+++ b/chrome/browser/resources/chromeos/login/md_login.js
@@ -9,7 +9,6 @@
 // <include src="test_util.js">
 // <include src="../../../../../ui/login/screen.js">
 // <include src="screen_context.js">
-// <include src="../user_images_grid.js">
 // <include src="apps_menu.js">
 // <include src="../../../../../ui/login/bubble.js">
 // <include src="../../../../../ui/login/display_manager.js">
@@ -17,10 +16,11 @@
 // <include src="demo_mode_test_helper.js">
 
 // <include
-// src="../../../../../ui/login/account_picker/md_screen_account_picker.js">
+// src="../../../../../ui/login/account_picker/chromeos_screen_account_picker.js">
 
 // <include src="../../../../../ui/login/login_ui_tools.js">
-// <include src="../../../../../ui/login/account_picker/md_user_pod_row.js">
+// <include
+// src="../../../../../ui/login/account_picker/chromeos_user_pod_row.js">
 // <include src="../../../../../ui/login/resource_loader.js">
 // <include src="cr_ui.js">
 // <include src="oobe_screen_reset.js">
@@ -56,28 +56,6 @@
 // <include src="screen_multidevice_setup.js">
 
 // <include src="../../gaia_auth_host/authenticator.js">
-
-// Register assets for async loading.
-[{
-  id: SCREEN_OOBE_ENROLLMENT,
-  html: [{url: 'chrome://oobe/enrollment.html', targetID: 'inner-container'}],
-  css: ['chrome://oobe/enrollment.css'],
-  js: ['chrome://oobe/enrollment.js']
-}].forEach(cr.ui.login.ResourceLoader.registerAssets);
-
-(function() {
-'use strict';
-
-document.addEventListener('DOMContentLoaded', function() {
-  // Immediately load async assets.
-  cr.ui.login.ResourceLoader.loadAssets(SCREEN_OOBE_ENROLLMENT, function() {
-    // This screen is async-loaded so we manually trigger i18n processing.
-    i18nTemplate.process($('oauth-enrollment'), loadTimeData);
-    // Delayed binding since this isn't defined yet.
-    login.OAuthEnrollmentScreen.register();
-  });
-});
-})();
 // <include src="notification_card.js">
 
 /**
@@ -144,11 +122,6 @@
     },
 
     // Dummy Oobe functions not present with stripped login UI.
-    initializeA11yMenu: function(e) {},
-    handleAccessibilityLinkClick: function(e) {},
-    handleSpokenFeedbackClick: function(e) {},
-    handleHighContrastClick: function(e) {},
-    handleScreenMagnifierClick: function(e) {},
     setUsageStats: function(checked) {},
     setTpmPassword: function(password) {},
     refreshA11yInfo: function(data) {},
diff --git a/chrome/browser/resources/chromeos/login/md_login_screens.html b/chrome/browser/resources/chromeos/login/md_login_screens.html
index 0e33276a..12dc1abc 100644
--- a/chrome/browser/resources/chromeos/login/md_login_screens.html
+++ b/chrome/browser/resources/chromeos/login/md_login_screens.html
@@ -5,7 +5,7 @@
 <include src="oobe_screen_user_image.html">
 <include src="oobe_screen_supervision_transition.html">
 <include src="oobe_screen_assistant_optin_flow.html">
-<include src="../../../../../ui/login/account_picker/md_screen_account_picker.html">
+<include src="../../../../../ui/login/account_picker/chromeos_screen_account_picker.html">
 <include src="screen_arc_terms_of_service.html">
 <include src="screen_error_message.html">
 <include src="screen_gaia_signin.html">
diff --git a/chrome/browser/resources/chromeos/login/md_screen_container.html b/chrome/browser/resources/chromeos/login/md_screen_container.html
index 480c4c15..069b4e1 100644
--- a/chrome/browser/resources/chromeos/login/md_screen_container.html
+++ b/chrome/browser/resources/chromeos/login/md_screen_container.html
@@ -18,5 +18,5 @@
 <div id="bubble" class="bubble faded" hidden></div>
 <include src="md_top_header_bar.html">
 <include src="md_header_bar.html">
-<include src="../../../../../ui/login/account_picker/md_user_pod_template.html">
+<include src="../../../../../ui/login/account_picker/chromeos_user_pod_template.html">
 <include src="oobe_screen_reset_confirmation_overlay.html">
diff --git a/chrome/browser/resources/chromeos/login/oobe.html b/chrome/browser/resources/chromeos/login/oobe.html
index 2cc1b66..f7c3c386 100644
--- a/chrome/browser/resources/chromeos/login/oobe.html
+++ b/chrome/browser/resources/chromeos/login/oobe.html
@@ -30,8 +30,8 @@
 <link rel="stylesheet" href="oobe_screen.css">
 
 <link rel="stylesheet" href="../../../../../ui/login/md_screen_container.css">
-<link rel="stylesheet" href="../../../../../ui/login/account_picker/md_screen_account_picker.css">
-<link rel="stylesheet" href="../../../../../ui/login/account_picker/md_user_pod_row.css">
+<link rel="stylesheet" href="../../../../../ui/login/account_picker/chromeos_screen_account_picker.css">
+<link rel="stylesheet" href="../../../../../ui/login/account_picker/chromeos_user_pod_row.css">
 
 <script src="chrome://resources/js/cr.js"></script>
 <script src="chrome://resources/js/event_tracker.js"></script>
@@ -86,12 +86,10 @@
 
 <link rel="import" href="chrome://resources/html/polymer.html">
 <link rel="import" href="chrome://oobe/custom_elements.html">
-<link rel="stylesheet" href="accessibility_menu.css">
 <script src="chrome://oobe/oobe.js"></script>
 </head>
 <body class="oobe-display chromeos" i18n-values=".style.fontFamily:fontfamily;">
   <include src="md_screen_container.html">
-  <include src="accessibility_menu.html">
   <div id="popup-overlay" class="popup-overlay" hidden>
     <include src="oobe_screen_eula_installation_settings_overlay.html">
   </div>
diff --git a/chrome/browser/resources/chromeos/login/oobe.js b/chrome/browser/resources/chromeos/login/oobe.js
index 3446c7cc..5bebe511e 100644
--- a/chrome/browser/resources/chromeos/login/oobe.js
+++ b/chrome/browser/resources/chromeos/login/oobe.js
@@ -10,7 +10,6 @@
 // <include src="test_util.js">
 // <include src="../../../../../ui/login/screen.js">
 // <include src="screen_context.js">
-// <include src="../user_images_grid.js">
 // <include src="apps_menu.js">
 // <include src="../../../../../ui/login/bubble.js">
 // <include src="../../../../../ui/login/display_manager.js">
@@ -18,10 +17,11 @@
 // <include src="demo_mode_test_helper.js">
 
 // <include
-// src="../../../../../ui/login/account_picker/md_screen_account_picker.js">
+// src="../../../../../ui/login/account_picker/chromeos_screen_account_picker.js">
 
 // <include src="../../../../../ui/login/login_ui_tools.js">
-// <include src="../../../../../ui/login/account_picker/md_user_pod_row.js">
+// <include
+// src="../../../../../ui/login/account_picker/chromeos_user_pod_row.js">
 // <include src="../../../../../ui/login/resource_loader.js">
 // <include src="cr_ui.js">
 // <include src="oobe_screen_reset.js">
@@ -144,152 +144,10 @@
       cr.ui.Bubble.decorate($('bubble'));
       login.HeaderBar.decorate($('login-header-bar'));
 
-      Oobe.initializeA11yMenu();
-
       chrome.send('screenStateInitialize');
     },
 
     /**
-     * Initializes OOBE accessibility menu.
-     */
-    initializeA11yMenu: function() {
-      cr.ui.Bubble.decorate($('accessibility-menu'));
-      // Same behaviour on hitting spacebar. See crbug.com/342991.
-      function reactOnSpace(event) {
-        if (event.keyCode == 32)
-          Oobe.handleAccessibilityLinkClick(event);
-      }
-
-      $('high-contrast')
-          .addEventListener('click', Oobe.handleHighContrastClick);
-      $('large-cursor').addEventListener('click', Oobe.handleLargeCursorClick);
-      $('spoken-feedback')
-          .addEventListener('click', Oobe.handleSpokenFeedbackClick);
-      $('select-to-speak')
-          .addEventListener('click', Oobe.handleSelectToSpeakClick);
-      $('screen-magnifier')
-          .addEventListener('click', Oobe.handleScreenMagnifierClick);
-      $('virtual-keyboard')
-          .addEventListener('click', Oobe.handleVirtualKeyboardClick);
-
-      $('high-contrast').addEventListener('keypress', Oobe.handleA11yKeyPress);
-      $('large-cursor').addEventListener('keypress', Oobe.handleA11yKeyPress);
-      $('spoken-feedback')
-          .addEventListener('keypress', Oobe.handleA11yKeyPress);
-      $('select-to-speak')
-          .addEventListener('keypress', Oobe.handleA11yKeyPress);
-      $('screen-magnifier')
-          .addEventListener('keypress', Oobe.handleA11yKeyPress);
-      $('virtual-keyboard')
-          .addEventListener('keypress', Oobe.handleA11yKeyPress);
-
-      // A11y menu should be accessible i.e. disable autohide on any
-      // keydown or click inside menu.
-      $('accessibility-menu').hideOnKeyPress = false;
-      $('accessibility-menu').hideOnSelfClick = false;
-    },
-
-    /**
-     * Accessibility link handler.
-     */
-    handleAccessibilityLinkClick: function(e) {
-      /** @const */ var BUBBLE_OFFSET = 5;
-      /** @const */ var BUBBLE_PADDING = 10;
-      $('accessibility-menu')
-          .showForElement(
-              e.target, cr.ui.Bubble.Attachment.BOTTOM, BUBBLE_OFFSET,
-              BUBBLE_PADDING);
-
-      var maxHeight = cr.ui.LoginUITools.getMaxHeightBeforeShelfOverlapping(
-          $('accessibility-menu'));
-      if (maxHeight < $('accessibility-menu').offsetHeight) {
-        $('accessibility-menu')
-            .showForElement(
-                e.target, cr.ui.Bubble.Attachment.TOP, BUBBLE_OFFSET,
-                BUBBLE_PADDING);
-      }
-
-      $('accessibility-menu').firstBubbleElement = $('spoken-feedback');
-      $('accessibility-menu').lastBubbleElement = $('close-accessibility-menu');
-      $('spoken-feedback').focus();
-
-      if (Oobe.getInstance().currentScreen &&
-          Oobe.getInstance().currentScreen.defaultControl) {
-        $('accessibility-menu').elementToFocusOnHide =
-            Oobe.getInstance().currentScreen.defaultControl;
-      } else {
-        // Update screen falls into this category. Since it doesn't have any
-        // controls other than a11y link we don't want that link to receive
-        // focus when screen is shown i.e. defaultControl is not defined.
-        // Focus a11y link instead.
-        $('accessibility-menu').elementToFocusOnHide = e.target;
-      }
-      e.stopPropagation();
-    },
-
-    /**
-     * handle a11y menu checkboxes keypress event by simulating click event.
-     */
-    handleA11yKeyPress: function(e) {
-      if (e.key != 'Enter')
-        return;
-
-      if (e.target.tagName != 'INPUT' || e.target.type != 'checkbox')
-        return;
-
-      // Simulate click on the checkbox.
-      e.target.click();
-    },
-
-    /**
-     * Spoken feedback checkbox handler.
-     */
-    handleSpokenFeedbackClick: function(e) {
-      chrome.send('enableSpokenFeedback', [$('spoken-feedback').checked]);
-      e.stopPropagation();
-    },
-
-    /**
-     * Select to speak checkbox handler.
-     */
-    handleSelectToSpeakClick: function(e) {
-      chrome.send('enableSelectToSpeak', [$('select-to-speak').checked]);
-      e.stopPropagation();
-    },
-
-    /**
-     * Large cursor checkbox handler.
-     */
-    handleLargeCursorClick: function(e) {
-      chrome.send('enableLargeCursor', [$('large-cursor').checked]);
-      e.stopPropagation();
-    },
-
-    /**
-     * High contrast mode checkbox handler.
-     */
-    handleHighContrastClick: function(e) {
-      chrome.send('enableHighContrast', [$('high-contrast').checked]);
-      e.stopPropagation();
-    },
-
-    /**
-     * Screen magnifier checkbox handler.
-     */
-    handleScreenMagnifierClick: function(e) {
-      chrome.send('enableScreenMagnifier', [$('screen-magnifier').checked]);
-      e.stopPropagation();
-    },
-
-    /**
-     * On-screen keyboard checkbox handler.
-     */
-    handleVirtualKeyboardClick: function(e) {
-      chrome.send('enableVirtualKeyboard', [$('virtual-keyboard').checked]);
-      e.stopPropagation();
-    },
-
-    /**
      * Sets usage statistics checkbox.
      * @param {boolean} checked Is the checkbox checked?
      */
@@ -310,18 +168,6 @@
      * @param {!Object} data New dictionary with a11y features state.
      */
     refreshA11yInfo: function(data) {
-      $('high-contrast').checked = data.highContrastEnabled;
-      $('spoken-feedback').checked = data.spokenFeedbackEnabled;
-      $('select-to-speak').checked = data.selectToSpeakEnabled;
-      $('screen-magnifier').checked = data.screenMagnifierEnabled;
-      $('docked-magnifier').checked = data.dockedMagnifierEnabled;
-      $('large-cursor').checked = data.largeCursorEnabled;
-      $('virtual-keyboard').checked = data.virtualKeyboardEnabled;
-
-      // TODO(katie): Remove this when launching features in OOBE screen.
-      if (!data.enableExperimentalA11yFeatures)
-        $('docked-magnifier-row').setAttribute('hidden', true);
-
       $('oobe-welcome-md').a11yStatus = data;
     },
 
diff --git a/chrome/browser/resources/chromeos/login/oobe_screens.html b/chrome/browser/resources/chromeos/login/oobe_screens.html
index 714694f..ba033078 100644
--- a/chrome/browser/resources/chromeos/login/oobe_screens.html
+++ b/chrome/browser/resources/chromeos/login/oobe_screens.html
@@ -14,7 +14,7 @@
 <include src="oobe_screen_demo_setup.html">
 <include src="oobe_screen_demo_preferences.html">
 <include src="oobe_screen_assistant_optin_flow.html">
-<include src="../../../../../ui/login/account_picker/md_screen_account_picker.html">
+<include src="../../../../../ui/login/account_picker/chromeos_screen_account_picker.html">
 <include src="screen_error_message.html">
 <include src="screen_arc_terms_of_service.html">
 <include src="screen_gaia_signin.html">
diff --git a/chrome/browser/resources/chromeos/login/saml_interstitial.html b/chrome/browser/resources/chromeos/login/saml_interstitial.html
index 61b82a06..76c9f43c 100644
--- a/chrome/browser/resources/chromeos/login/saml_interstitial.html
+++ b/chrome/browser/resources/chromeos/login/saml_interstitial.html
@@ -1,5 +1,7 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
+<include src="html-echo.html">
+
 <!--
 UI for the SAML interstitial page.
 Example:
diff --git a/chrome/browser/resources/chromeos/login/saml_interstitial.js b/chrome/browser/resources/chromeos/login/saml_interstitial.js
index 7da3ee2..a4ae4854 100644
--- a/chrome/browser/resources/chromeos/login/saml_interstitial.js
+++ b/chrome/browser/resources/chromeos/login/saml_interstitial.js
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// <include src="html-echo.js">
+
 Polymer({
   is: 'saml-interstitial',
 
diff --git a/chrome/browser/resources/chromeos/user_images_grid.js b/chrome/browser/resources/chromeos/user_images_grid.js
deleted file mode 100644
index b0002cfa..0000000
--- a/chrome/browser/resources/chromeos/user_images_grid.js
+++ /dev/null
@@ -1,653 +0,0 @@
-// Copyright (c) 2012 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.
-
-cr.define('options', function() {
-  /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
-  /** @const */ var Grid = cr.ui.Grid;
-  /** @const */ var GridItem = cr.ui.GridItem;
-  /** @const */ var GridSelectionController = cr.ui.GridSelectionController;
-  /** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
-
-  /**
-   * Dimensions for camera capture.
-   * @const
-   */
-  var CAPTURE_SIZE = {height: 576, width: 576};
-
-  /**
-   * Path for internal URLs.
-   * @const
-   */
-  var CHROME_THEME_PATH = 'chrome://theme';
-
-  /**
-   * Creates a new user images grid item.
-   * @param {{url: string, title: (string|undefined),
-   *     decorateFn: (!Function|undefined),
-   *     clickHandler: (!Function|undefined)}} imageInfo User image URL,
-   *     optional title, decorator callback and click handler.
-   * @constructor
-   * @extends {cr.ui.GridItem}
-   */
-  function UserImagesGridItem(imageInfo) {
-    var el = new GridItem(imageInfo);
-    el.__proto__ = UserImagesGridItem.prototype;
-    return el;
-  }
-
-  UserImagesGridItem.prototype = {
-    __proto__: GridItem.prototype,
-
-    /** @override */
-    decorate: function() {
-      GridItem.prototype.decorate.call(this);
-      var imageEl = cr.doc.createElement('img');
-      // Force 1x scale for chrome://theme URLs. Grid elements are much smaller
-      // than actual images so there is no need in full scale on HDPI.
-      var url = this.dataItem.url;
-      if (url.slice(0, CHROME_THEME_PATH.length) == CHROME_THEME_PATH)
-        imageEl.src = this.dataItem.url + '[0]@1x';
-      else
-        imageEl.src = this.dataItem.url;
-      imageEl.title = this.dataItem.title || '';
-      imageEl.alt = imageEl.title;
-      if (typeof this.dataItem.clickHandler == 'function')
-        imageEl.addEventListener('mousedown', this.dataItem.clickHandler);
-      // Remove any garbage added by GridItem and ListItem decorators.
-      this.textContent = '';
-      this.appendChild(imageEl);
-      if (typeof this.dataItem.decorateFn == 'function')
-        this.dataItem.decorateFn(this);
-      this.setAttribute('role', 'option');
-      this.oncontextmenu = function(e) {
-        e.preventDefault();
-      };
-    }
-  };
-
-  /**
-   * Creates a selection controller that wraps selection on grid ends
-   * and translates Enter presses into 'activate' events.
-   * @param {cr.ui.ListSelectionModel} selectionModel The selection model to
-   *     interact with.
-   * @param {cr.ui.Grid} grid The grid to interact with.
-   * @constructor
-   * @extends {cr.ui.GridSelectionController}
-   */
-  function UserImagesGridSelectionController(selectionModel, grid) {
-    GridSelectionController.call(this, selectionModel, grid);
-  }
-
-  UserImagesGridSelectionController.prototype = {
-    __proto__: GridSelectionController.prototype,
-
-    /** @override */
-    getIndexBefore: function(index) {
-      var result =
-          GridSelectionController.prototype.getIndexBefore.call(this, index);
-      return result == -1 ? this.getLastIndex() : result;
-    },
-
-    /** @override */
-    getIndexAfter: function(index) {
-      var result =
-          GridSelectionController.prototype.getIndexAfter.call(this, index);
-      return result == -1 ? this.getFirstIndex() : result;
-    },
-
-    /** @override */
-    handleKeyDown: function(e) {
-      if (e.key == 'Enter')
-        cr.dispatchSimpleEvent(this.grid_, 'activate');
-      else
-        GridSelectionController.prototype.handleKeyDown.call(this, e);
-    }
-  };
-
-  /**
-   * Creates a new user images grid element.
-   * @param {Object=} opt_propertyBag Optional properties.
-   * @constructor
-   * @extends {cr.ui.Grid}
-   */
-  var UserImagesGrid = cr.ui.define('grid');
-
-  UserImagesGrid.prototype = {
-    __proto__: Grid.prototype,
-
-    /** @override */
-    createSelectionController: function(sm) {
-      return new UserImagesGridSelectionController(sm, this);
-    },
-
-    /** @override */
-    decorate: function() {
-      Grid.prototype.decorate.call(this);
-      this.dataModel = new ArrayDataModel([]);
-      this.itemConstructor =
-          /** @type {function(new:cr.ui.ListItem, *)} */ (UserImagesGridItem);
-      this.selectionModel = new ListSingleSelectionModel();
-      this.inProgramSelection_ = false;
-      this.addEventListener('dblclick', this.handleDblClick_.bind(this));
-      this.addEventListener('change', this.handleChange_.bind(this));
-      this.setAttribute('role', 'listbox');
-      this.autoExpands = true;
-    },
-
-    /**
-     * Handles double click on the image grid.
-     * @param {Event} e Double click Event.
-     * @private
-     */
-    handleDblClick_: function(e) {
-      // If a child element is double-clicked and not the grid itself, handle
-      // this as 'Enter' keypress.
-      if (e.target != this)
-        cr.dispatchSimpleEvent(this, 'activate');
-    },
-
-    /**
-     * Handles selection change.
-     * @param {Event} e Double click Event.
-     * @private
-     */
-    handleChange_: function(e) {
-      if (this.selectedItem === null)
-        return;
-
-      var oldSelectionType = this.selectionType;
-
-      // Update current selection type.
-      this.selectionType = this.selectedItem.type;
-
-      // Show grey silhouette with the same border as stock images.
-      if (/^chrome:\/\/theme\//.test(this.selectedItemUrl))
-        this.previewElement.classList.add('default-image');
-
-      this.updatePreview_();
-
-      var e = new Event('select');
-      e.oldSelectionType = oldSelectionType;
-      this.dispatchEvent(e);
-    },
-
-    /**
-     * Updates the preview image, if present.
-     * @private
-     */
-    updatePreview_: function() {
-      var url = this.selectedItemUrl;
-      if (url && this.previewImage_) {
-        if (url.slice(0, CHROME_THEME_PATH.length) == CHROME_THEME_PATH)
-          this.previewImage_.src = url + '@2x';
-        else
-          this.previewImage_.src = url;
-      }
-    },
-
-    /**
-     * Whether a camera is present or not.
-     * @type {boolean}
-     */
-    get cameraPresent() {
-      return this.cameraPresent_;
-    },
-    set cameraPresent(value) {
-      this.cameraPresent_ = value;
-      if (this.cameraLive)
-        this.cameraImage = null;
-    },
-
-    /**
-     * Whether camera is actually streaming video. May be |false| even when
-     * camera is present and shown but still initializing.
-     * @type {boolean}
-     */
-    get cameraOnline() {
-      return this.previewElement.classList.contains('online');
-    },
-    set cameraOnline(value) {
-      this.previewElement.classList.toggle('online', value);
-    },
-
-    /**
-     * Tries to starts camera stream capture.
-     * @param {function(): boolean} onAvailable Callback that is called if
-     *     camera is available. If it returns |true|, capture is started
-     *     immediately.
-     */
-    startCamera: function(onAvailable, onAbsent) {
-      this.stopCamera();
-      this.cameraStartInProgress_ = true;
-      navigator.webkitGetUserMedia(
-          {video: true}, this.handleCameraAvailable_.bind(this, onAvailable),
-          this.handleCameraAbsent_.bind(this));
-    },
-
-    /**
-     * Stops camera capture, if it's currently active.
-     */
-    stopCamera: function() {
-      this.cameraOnline = false;
-      if (this.cameraVideo_)
-        this.cameraVideo_.srcObject = null;
-      if (this.cameraStream_) {
-        this.stopVideoTracks_(this.cameraStream_);
-        this.cameraStream_ = null;
-      }
-      // Cancel any pending getUserMedia() checks.
-      this.cameraStartInProgress_ = false;
-    },
-
-    /**
-     * Stops all video tracks associated with a MediaStream object.
-     * @param {MediaStream} stream
-     */
-    stopVideoTracks_: function(stream) {
-      var tracks = stream.getVideoTracks();
-      for (var t of tracks)
-        t.stop();
-    },
-
-    /**
-     * Handles successful camera check.
-     * @param {function(): boolean} onAvailable Callback to call. If it returns
-     *     |true|, capture is started immediately.
-     * @param {!MediaStream} stream Stream object as returned by getUserMedia.
-     * @private
-     * @suppress {deprecated}
-     */
-    handleCameraAvailable_: function(onAvailable, stream) {
-      if (this.cameraStartInProgress_ && onAvailable()) {
-        this.cameraVideo_.srcObject = stream;
-        this.cameraStream_ = stream;
-      } else {
-        this.stopVideoTracks_(stream);
-      }
-      this.cameraStartInProgress_ = false;
-    },
-
-    /**
-     * Handles camera check failure.
-     * @param {NavigatorUserMediaError=} err Error object.
-     * @private
-     */
-    handleCameraAbsent_: function(err) {
-      this.cameraPresent = false;
-      this.cameraOnline = false;
-      this.cameraStartInProgress_ = false;
-    },
-
-    /**
-     * Handles successful camera capture start.
-     * @private
-     */
-    handleVideoStarted_: function() {
-      this.cameraOnline = true;
-      this.handleVideoUpdate_();
-    },
-
-    /**
-     * Handles camera stream update. Called regularly (at rate no greater than
-     * 4/sec) while camera stream is live.
-     * @private
-     */
-    handleVideoUpdate_: function() {
-      this.lastFrameTime_ = new Date().getTime();
-    },
-
-    /**
-     * Type of the selected image (one of 'default', 'profile', 'camera').
-     * Setting it will update class list of |previewElement|.
-     * @type {string}
-     */
-    get selectionType() {
-      return this.selectionType_;
-    },
-    set selectionType(value) {
-      this.selectionType_ = value;
-      var previewClassList = this.previewElement.classList;
-      previewClassList[value == 'default' ? 'add' : 'remove']('default-image');
-      previewClassList[value == 'profile' ? 'add' : 'remove']('profile-image');
-      previewClassList[value == 'camera' ? 'add' : 'remove']('camera');
-      if (!$('user-image-grid'))
-        return;
-
-      var setFocusIfLost = function() {
-        // Set focus to the grid, if focus is not on UI.
-        if (!document.activeElement ||
-            document.activeElement.tagName == 'BODY') {
-          $('user-image-grid').focus();
-        }
-      };
-      // Timeout guarantees processing AFTER style changes display attribute.
-      setTimeout(setFocusIfLost, 0);
-    },
-
-    /**
-     * Current image captured from camera as data URL. Setting to null will
-     * return to the live camera stream.
-     * @type {(string|undefined)}
-     */
-    get cameraImage() {
-      return this.cameraImage_;
-    },
-    set cameraImage(imageUrl) {
-      this.cameraLive = !imageUrl;
-      if (this.cameraPresent && !imageUrl)
-        imageUrl = UserImagesGrid.ButtonImages.TAKE_PHOTO;
-      if (imageUrl) {
-        this.cameraImage_ = this.cameraImage_ ?
-            this.updateItem(this.cameraImage_, imageUrl, this.cameraTitle_) :
-            this.addItem(imageUrl, this.cameraTitle_, undefined, 0);
-        this.cameraImage_.type = 'camera';
-      } else {
-        this.removeItem(this.cameraImage_);
-        this.cameraImage_ = null;
-      }
-    },
-
-    /**
-     * Updates the titles for the camera element.
-     * @param {string} placeholderTitle Title when showing a placeholder.
-     * @param {string} capturedImageTitle Title when showing a captured photo.
-     */
-    setCameraTitles: function(placeholderTitle, capturedImageTitle) {
-      this.placeholderTitle_ = placeholderTitle;
-      this.capturedImageTitle_ = capturedImageTitle;
-      this.cameraTitle_ = this.placeholderTitle_;
-    },
-
-    /**
-     * True when camera is in live mode (i.e. no still photo selected).
-     * @type {boolean}
-     */
-    get cameraLive() {
-      return this.cameraLive_;
-    },
-    set cameraLive(value) {
-      this.cameraLive_ = value;
-      this.previewElement.classList[value ? 'add' : 'remove']('live');
-    },
-
-    /**
-     * Should only be queried from the 'change' event listener, true if the
-     * change event was triggered by a programmatical selection change.
-     * @type {boolean}
-     */
-    get inProgramSelection() {
-      return this.inProgramSelection_;
-    },
-
-    /**
-     * URL of the image selected.
-     * @type {string?}
-     */
-    get selectedItemUrl() {
-      var selectedItem = this.selectedItem;
-      return selectedItem ? selectedItem.url : null;
-    },
-    set selectedItemUrl(url) {
-      for (var i = 0, el; el = this.dataModel.item(i); i++) {
-        if (el.url === url)
-          this.selectedItemIndex = i;
-      }
-    },
-
-    /**
-     * Set index to the image selected.
-     * @type {number} index The index of selected image.
-     */
-    set selectedItemIndex(index) {
-      this.inProgramSelection_ = true;
-      this.selectionModel.selectedIndex = index;
-      this.inProgramSelection_ = false;
-    },
-
-    /** @override */
-    get selectedItem() {
-      var index = this.selectionModel.selectedIndex;
-      return index != -1 ? this.dataModel.item(index) : null;
-    },
-    set selectedItem(selectedItem) {
-      var index = this.indexOf(selectedItem);
-      this.inProgramSelection_ = true;
-      this.selectionModel.selectedIndex = index;
-      this.selectionModel.leadIndex = index;
-      this.inProgramSelection_ = false;
-    },
-
-    /**
-     * Element containing the preview image (the first IMG element) and the
-     * camera live stream (the first VIDEO element).
-     * @type {HTMLElement}
-     */
-    get previewElement() {
-      // TODO(ivankr): temporary hack for non-HTML5 version.
-      return this.previewElement_ || this;
-    },
-    set previewElement(value) {
-      this.previewElement_ = value;
-      this.previewImage_ = value.querySelector('img');
-      this.cameraVideo_ = value.querySelector('video');
-      this.cameraVideo_.addEventListener(
-          'canplay', this.handleVideoStarted_.bind(this));
-      this.cameraVideo_.addEventListener(
-          'timeupdate', this.handleVideoUpdate_.bind(this));
-      this.updatePreview_();
-      // Initialize camera state and check for its presence.
-      this.cameraLive = true;
-      this.cameraPresent = false;
-    },
-
-    /**
-     * Performs photo capture from the live camera stream. 'phototaken' event
-     * will be fired as soon as captured photo is available, with 'dataURL'
-     * property containing the photo encoded as a data URL.
-     * @return {boolean} Whether photo capture was successful.
-     */
-    takePhoto: function() {
-      if (!this.cameraOnline)
-        return false;
-      var canvas =
-          /** @type {HTMLCanvasElement} */ (document.createElement('canvas'));
-      canvas.width = CAPTURE_SIZE.width;
-      canvas.height = CAPTURE_SIZE.height;
-      this.captureFrame_(
-          this.cameraVideo_,
-          /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d')),
-          CAPTURE_SIZE);
-      // Preload image before displaying it.
-      var previewImg = new Image();
-      previewImg.addEventListener('load', function(e) {
-        this.cameraTitle_ = this.capturedImageTitle_;
-        this.cameraImage = previewImg.src;
-      }.bind(this));
-      var imageUrl = this.flipFrame_(canvas);
-      previewImg.src = imageUrl;
-      var e = new Event('phototaken');
-      e.dataURL = imageUrl;
-      this.dispatchEvent(e);
-      return true;
-    },
-
-    /**
-     * Discard current photo and return to the live camera stream.
-     */
-    discardPhoto: function() {
-      this.cameraTitle_ = this.placeholderTitle_;
-      this.cameraImage = null;
-    },
-
-    /**
-     * Capture a single still frame from a <video> element, placing it at the
-     * current drawing origin of a canvas context.
-     * @param {HTMLVideoElement} video Video element to capture from.
-     * @param {CanvasRenderingContext2D} ctx Canvas context to draw onto.
-     * @param {{width: number, height: number}} destSize Capture size.
-     * @private
-     */
-    captureFrame_: function(video, ctx, destSize) {
-      var width = video.videoWidth;
-      var height = video.videoHeight;
-      if (width < destSize.width || height < destSize.height) {
-        console.error(
-            'Video capture size too small: ' + width + 'x' + height + '!');
-      }
-      var src = {};
-      if (width / destSize.width > height / destSize.height) {
-        // Full height, crop left/right.
-        src.height = height;
-        src.width = height * destSize.width / destSize.height;
-      } else {
-        // Full width, crop top/bottom.
-        src.width = width;
-        src.height = width * destSize.height / destSize.width;
-      }
-      src.x = (width - src.width) / 2;
-      src.y = (height - src.height) / 2;
-      ctx.drawImage(
-          video, src.x, src.y, src.width, src.height, 0, 0, destSize.width,
-          destSize.height);
-    },
-
-    /**
-     * Flips frame horizontally.
-     * @param {HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} source
-     *     Frame to flip.
-     * @return {string} Flipped frame as data URL.
-     */
-    flipFrame_: function(source) {
-      var canvas = document.createElement('canvas');
-      canvas.width = CAPTURE_SIZE.width;
-      canvas.height = CAPTURE_SIZE.height;
-      var ctx = canvas.getContext('2d');
-      ctx.translate(CAPTURE_SIZE.width, 0);
-      ctx.scale(-1.0, 1.0);
-      ctx.drawImage(source, 0, 0);
-      return canvas.toDataURL('image/png');
-    },
-
-    /**
-     * Adds new image to the user image grid.
-     * @param {string} url Image URL.
-     * @param {string=} opt_title Image tooltip.
-     * @param {Function=} opt_clickHandler Image click handler.
-     * @param {number=} opt_position If given, inserts new image into
-     *     that position (0-based) in image list.
-     * @param {Function=} opt_decorateFn Function called with the list element
-     *     as argument to do any final decoration.
-     * @return {!Object} Image data inserted into the data model.
-     */
-    // TODO(ivankr): this function needs some argument list refactoring.
-    addItem: function(
-        url, opt_title, opt_clickHandler, opt_position, opt_decorateFn) {
-      var imageInfo = {
-        url: url,
-        title: opt_title,
-        clickHandler: opt_clickHandler,
-        decorateFn: opt_decorateFn
-      };
-      this.inProgramSelection_ = true;
-      if (opt_position !== undefined)
-        this.dataModel.splice(opt_position, 0, imageInfo);
-      else
-        this.dataModel.push(imageInfo);
-      this.inProgramSelection_ = false;
-      return imageInfo;
-    },
-
-    /**
-     * Returns index of an image in grid.
-     * @param {Object} imageInfo Image data returned from addItem() call.
-     * @return {number} Image index (0-based) or -1 if image was not found.
-     */
-    indexOf: function(imageInfo) {
-      return this.dataModel.indexOf(imageInfo);
-    },
-
-    /**
-     * Replaces an image in the grid.
-     * @param {Object} imageInfo Image data returned from addItem() call.
-     * @param {string} imageUrl New image URL.
-     * @param {string=} opt_title New image tooltip (if undefined, tooltip
-     *     is left unchanged).
-     * @return {!Object} Image data of the added or updated image.
-     */
-    updateItem: function(imageInfo, imageUrl, opt_title) {
-      var imageIndex = this.indexOf(imageInfo);
-      var wasSelected = this.selectionModel.selectedIndex == imageIndex;
-      this.removeItem(imageInfo);
-      var newInfo = this.addItem(
-          imageUrl, opt_title === undefined ? imageInfo.title : opt_title,
-          imageInfo.clickHandler, imageIndex, imageInfo.decorateFn);
-      // Update image data with the reset of the keys from the old data.
-      for (var k in imageInfo) {
-        if (!(k in newInfo))
-          newInfo[k] = imageInfo[k];
-      }
-      if (wasSelected)
-        this.selectedItem = newInfo;
-      return newInfo;
-    },
-
-    /**
-     * Removes previously added image from the grid.
-     * @param {Object} imageInfo Image data returned from the addItem() call.
-     */
-    removeItem: function(imageInfo) {
-      var index = this.indexOf(imageInfo);
-      if (index != -1) {
-        var wasSelected = this.selectionModel.selectedIndex == index;
-        this.inProgramSelection_ = true;
-        this.dataModel.splice(index, 1);
-        if (wasSelected) {
-          // If item removed was selected, select the item next to it.
-          this.selectedItem =
-              this.dataModel.item(Math.min(this.dataModel.length - 1, index));
-        }
-        this.inProgramSelection_ = false;
-      }
-    },
-
-    /**
-     * Forces re-display, size re-calculation and focuses grid.
-     */
-    updateAndFocus: function() {
-      // Recalculate the measured item size.
-      this.measured_ = null;
-      this.columns = 0;
-      this.redraw();
-      this.focus();
-    },
-
-    /**
-     * Appends default images to the image grid. Should only be called once.
-     * @param {Array<{url: string, author: string,
-     *                website: string, title: string}>} imagesData
-     *   An array of default images data, including URL, author, title and
-     *   website.
-     */
-    setDefaultImages: function(imagesData) {
-      for (var i = 0, data; data = imagesData[i]; i++) {
-        var item = this.addItem(data.url, data.title);
-        item.type = 'default';
-        item.author = data.author || '';
-        item.website = data.website || '';
-      }
-    }
-  };
-
-  /**
-   * URLs of special button images.
-   * @enum {string}
-   */
-  UserImagesGrid.ButtonImages = {
-    TAKE_PHOTO: 'chrome://theme/IDR_BUTTON_USER_IMAGE_TAKE_PHOTO',
-    CHOOSE_FILE: 'chrome://theme/IDR_BUTTON_USER_IMAGE_CHOOSE_FILE',
-    PROFILE_PICTURE: 'chrome://theme/IDR_LOGIN_DEFAULT_USER'
-  };
-
-  return {UserImagesGrid: UserImagesGrid};
-});
diff --git a/chrome/browser/resources/settings/crostini_page/crostini_browser_proxy.js b/chrome/browser/resources/settings/crostini_page/crostini_browser_proxy.js
index d4eba5b..6e4c97aa 100644
--- a/chrome/browser/resources/settings/crostini_page/crostini_browser_proxy.js
+++ b/chrome/browser/resources/settings/crostini_page/crostini_browser_proxy.js
@@ -29,6 +29,12 @@
 
     /** @param {string} path Path to stop sharing. */
     removeCrostiniSharedPath(path) {}
+
+    /* Export crostini container. */
+    exportCrostiniContainer() {}
+
+    /* Import crostini container. */
+    importCrostiniContainer() {}
   }
 
   /** @implements {settings.CrostiniBrowserProxy} */
@@ -52,6 +58,16 @@
     removeCrostiniSharedPath(path) {
       chrome.send('removeCrostiniSharedPath', [path]);
     }
+
+    /** @override */
+    exportCrostiniContainer() {
+      chrome.send('exportCrostiniContainer');
+    }
+
+    /** @override */
+    importCrostiniContainer() {
+      chrome.send('importCrostiniContainer');
+    }
   }
 
   // The singleton instance_ can be replaced with a test version of this wrapper
diff --git a/chrome/browser/resources/settings/crostini_page/crostini_subpage.html b/chrome/browser/resources/settings/crostini_page/crostini_subpage.html
index f6fc52b..a83dd31e 100644
--- a/chrome/browser/resources/settings/crostini_page/crostini_subpage.html
+++ b/chrome/browser/resources/settings/crostini_page/crostini_subpage.html
@@ -27,6 +27,23 @@
         </paper-icon-button-light>
       </div>
     </template>
+    <template is="dom-if" if="[[showCrostiniExportImport_]]">
+      <div class="settings-box">$i18n{crostiniExportImportTitle}</div>
+      <div class="list-frame vertical-list">
+        <div id="export" class="list-item">
+          <div class="start secondary">$i18n{crostiniExportLabel}</div>
+          <paper-button on-click="onExportClick_">
+             $i18n{crostiniExport}
+          </paper-button>
+        </div>
+        <div id="import" class="list-item">
+          <div class="start secondary">$i18n{crostiniImportLabel}</div>
+          <paper-button on-click="onImportClick_">
+             $i18n{crostiniImport}
+          </paper-button>
+        </div>
+      </div>
+    </template>
     <div id="remove" class="settings-box"
         actionable on-click="onRemoveTap_">
       <div class="start">$i18n{crostiniRemove}</div>
diff --git a/chrome/browser/resources/settings/crostini_page/crostini_subpage.js b/chrome/browser/resources/settings/crostini_page/crostini_subpage.js
index 7d8ac3c..d671051 100644
--- a/chrome/browser/resources/settings/crostini_page/crostini_subpage.js
+++ b/chrome/browser/resources/settings/crostini_page/crostini_subpage.js
@@ -30,6 +30,16 @@
       },
     },
 
+    /**
+     * Whether export / import UI should be displayed.
+     * @private {boolean}
+     */
+    showCrostiniExportImport_: {
+      type: Boolean,
+      value: function() {
+        return loadTimeData.getBoolean('showCrostiniExportImport');
+      },
+    },
   },
 
   observers: ['onCrostiniEnabledChanged_(prefs.crostini.enabled.value)'],
@@ -57,6 +67,16 @@
   },
 
   /** @private */
+  onExportClick_: function(event) {
+    settings.CrostiniBrowserProxyImpl.getInstance().exportCrostiniContainer();
+  },
+
+  /** @private */
+  onImportClick_: function(event) {
+    settings.CrostiniBrowserProxyImpl.getInstance().importCrostiniContainer();
+  },
+
+  /** @private */
   onSharedUsbDevicesTap_: function(event) {
     settings.navigateTo(settings.routes.CROSTINI_SHARED_USB_DEVICES);
   },
diff --git a/chrome/browser/sessions/better_session_restore_browsertest.cc b/chrome/browser/sessions/better_session_restore_browsertest.cc
index 095c989..67d5f776 100644
--- a/chrome/browser/sessions/better_session_restore_browsertest.cc
+++ b/chrome/browser/sessions/better_session_restore_browsertest.cc
@@ -552,15 +552,8 @@
 
 // Check that cookies are cleared on a wrench menu quit only if cookies are set
 // to current session only, regardless of whether background mode is enabled.
-// Flaky on Windows and Linux: https://crbug.com/931777
-#if defined(OS_WIN) || defined(OS_LINUX)
-#define MAYBE_CookiesClearedOnCloseAllBrowsers \
-  DISABLED_CookiesClearedOnCloseAllBrowsers
-#else
-#define MAYBE_CookiesClearedOnCloseAllBrowsers CookiesClearedOnCloseAllBrowsers
-#endif
 IN_PROC_BROWSER_TEST_F(ContinueWhereILeftOffTest,
-                       MAYBE_CookiesClearedOnCloseAllBrowsers) {
+                       CookiesClearedOnCloseAllBrowsers) {
   StoreDataWithPage("cookies.html");
   // Normally cookies are restored.
   Browser* new_browser = QuitBrowserAndRestore(browser(), true);
diff --git a/chrome/browser/signin/identity_manager_factory.cc b/chrome/browser/signin/identity_manager_factory.cc
index 4da6fa5..58cd487 100644
--- a/chrome/browser/signin/identity_manager_factory.cc
+++ b/chrome/browser/signin/identity_manager_factory.cc
@@ -52,7 +52,7 @@
   return std::make_unique<identity::AccountsMutatorImpl>(
       ProfileOAuth2TokenServiceFactory::GetForProfile(profile),
       AccountTrackerServiceFactory::GetForProfile(profile),
-      SigninManagerFactory::GetForProfile(profile));
+      SigninManagerFactory::GetForProfile(profile), profile->GetPrefs());
 #else
   return nullptr;
 #endif
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 5ef6a665..fbe8461 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -2655,8 +2655,6 @@
       "views/overlay/close_image_button.h",
       "views/overlay/control_image_button.cc",
       "views/overlay/control_image_button.h",
-      "views/overlay/next_track_image_button.cc",
-      "views/overlay/next_track_image_button.h",
       "views/overlay/overlay_window_views.cc",
       "views/overlay/overlay_window_views.h",
       "views/overlay/playback_image_button.cc",
@@ -2665,6 +2663,8 @@
       "views/overlay/resize_handle_button.h",
       "views/overlay/skip_ad_label_button.cc",
       "views/overlay/skip_ad_label_button.h",
+      "views/overlay/track_image_button.cc",
+      "views/overlay/track_image_button.h",
       "views/page_action/page_action_icon_container_view.cc",
       "views/page_action/page_action_icon_container_view.h",
       "views/page_action/page_action_icon_view.cc",
diff --git a/chrome/browser/ui/views/overlay/back_to_tab_image_button.h b/chrome/browser/ui/views/overlay/back_to_tab_image_button.h
index 8436964..813527a 100644
--- a/chrome/browser/ui/views/overlay/back_to_tab_image_button.h
+++ b/chrome/browser/ui/views/overlay/back_to_tab_image_button.h
@@ -14,7 +14,7 @@
 // the button, a grey circular background appears as an indicator.
 class BackToTabImageButton : public views::ImageButton {
  public:
-  explicit BackToTabImageButton(ButtonListener* listener);
+  explicit BackToTabImageButton(ButtonListener*);
   ~BackToTabImageButton() override = default;
 
   // views::Button:
diff --git a/chrome/browser/ui/views/overlay/close_image_button.h b/chrome/browser/ui/views/overlay/close_image_button.h
index 6ff8e9a..7b58e6a 100644
--- a/chrome/browser/ui/views/overlay/close_image_button.h
+++ b/chrome/browser/ui/views/overlay/close_image_button.h
@@ -14,7 +14,7 @@
 // with the button, a grey circular background appears as an indicator.
 class CloseImageButton : public views::ImageButton {
  public:
-  explicit CloseImageButton(ButtonListener* listener);
+  explicit CloseImageButton(ButtonListener*);
   ~CloseImageButton() override = default;
 
   // views::Button:
diff --git a/chrome/browser/ui/views/overlay/control_image_button.h b/chrome/browser/ui/views/overlay/control_image_button.h
index dee2eb4..3063802 100644
--- a/chrome/browser/ui/views/overlay/control_image_button.h
+++ b/chrome/browser/ui/views/overlay/control_image_button.h
@@ -12,7 +12,7 @@
 // An image button with an associated id.
 class ControlImageButton : public views::ImageButton {
  public:
-  explicit ControlImageButton(ButtonListener* listener);
+  explicit ControlImageButton(ButtonListener*);
 
   ~ControlImageButton() override;
 
diff --git a/chrome/browser/ui/views/overlay/next_track_image_button.cc b/chrome/browser/ui/views/overlay/next_track_image_button.cc
deleted file mode 100644
index 2d19dad..0000000
--- a/chrome/browser/ui/views/overlay/next_track_image_button.cc
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2019 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 "chrome/browser/ui/views/overlay/next_track_image_button.h"
-
-#include "chrome/app/vector_icons/vector_icons.h"
-#include "chrome/grit/generated_resources.h"
-#include "components/vector_icons/vector_icons.h"
-#include "third_party/skia/include/core/SkColor.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/gfx/paint_vector_icon.h"
-#include "ui/views/vector_icons.h"
-
-namespace {
-
-SkColor kNextTrackIconColor = SK_ColorWHITE;
-
-}  // namespace
-
-namespace views {
-
-NextTrackImageButton::NextTrackImageButton(ButtonListener* listener)
-    : ImageButton(listener) {
-  SetImageAlignment(views::ImageButton::ALIGN_CENTER,
-                    views::ImageButton::ALIGN_MIDDLE);
-
-  // Accessibility.
-  SetFocusForPlatform();
-  const base::string16 next_track_button_label(l10n_util::GetStringUTF16(
-      IDS_PICTURE_IN_PICTURE_NEXT_TRACK_CONTROL_ACCESSIBLE_TEXT));
-  SetAccessibleName(next_track_button_label);
-  SetTooltipText(next_track_button_label);
-  SetInstallFocusRingOnFocus(true);
-}
-
-NextTrackImageButton::~NextTrackImageButton() = default;
-
-gfx::Size NextTrackImageButton::GetLastVisibleSize() const {
-  return size().IsEmpty() ? last_visible_size_ : size();
-}
-
-void NextTrackImageButton::OnBoundsChanged(const gfx::Rect&) {
-  SetImage(views::Button::STATE_NORMAL,
-           gfx::CreateVectorIcon(vector_icons::kMediaNextTrackIcon,
-                                 GetLastVisibleSize().width() / 2,
-                                 kNextTrackIconColor));
-}
-
-void NextTrackImageButton::ToggleVisibility(bool is_visible) {
-  if (is_visible && !size().IsEmpty()) {
-    last_visible_size_ = size();
-  }
-
-  SetVisible(is_visible);
-  SetEnabled(is_visible);
-  SetSize(is_visible ? GetLastVisibleSize() : gfx::Size());
-}
-
-}  // namespace views
diff --git a/chrome/browser/ui/views/overlay/next_track_image_button.h b/chrome/browser/ui/views/overlay/next_track_image_button.h
deleted file mode 100644
index 3f265cb30..0000000
--- a/chrome/browser/ui/views/overlay/next_track_image_button.h
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2019 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 CHROME_BROWSER_UI_VIEWS_OVERLAY_NEXT_TRACK_IMAGE_BUTTON_H_
-#define CHROME_BROWSER_UI_VIEWS_OVERLAY_NEXT_TRACK_IMAGE_BUTTON_H_
-
-#include "chrome/browser/ui/views/overlay/overlay_window_views.h"
-#include "ui/views/controls/button/image_button.h"
-
-namespace views {
-
-// A resizable next track image button.
-class NextTrackImageButton : public views::ImageButton {
- public:
-  explicit NextTrackImageButton(ButtonListener*);
-  ~NextTrackImageButton() override;
-
-  // Get button size when visible.
-  gfx::Size GetLastVisibleSize() const;
-
-  // Toggle visibility.
-  void ToggleVisibility(bool is_visible);
-
- protected:
-  // Overridden from views::View.
-  void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
-
- private:
-  // Last visible size of the image button.
-  gfx::Size last_visible_size_;
-
-  DISALLOW_COPY_AND_ASSIGN(NextTrackImageButton);
-};
-
-}  // namespace views
-
-#endif  // CHROME_BROWSER_UI_VIEWS_OVERLAY_NEXT_TRACK_IMAGE_BUTTON_H_
diff --git a/chrome/browser/ui/views/overlay/overlay_window_views.cc b/chrome/browser/ui/views/overlay/overlay_window_views.cc
index 08d35ae4..07d1e76 100644
--- a/chrome/browser/ui/views/overlay/overlay_window_views.cc
+++ b/chrome/browser/ui/views/overlay/overlay_window_views.cc
@@ -16,10 +16,10 @@
 #include "chrome/browser/ui/views/overlay/back_to_tab_image_button.h"
 #include "chrome/browser/ui/views/overlay/close_image_button.h"
 #include "chrome/browser/ui/views/overlay/control_image_button.h"
-#include "chrome/browser/ui/views/overlay/next_track_image_button.h"
 #include "chrome/browser/ui/views/overlay/playback_image_button.h"
 #include "chrome/browser/ui/views/overlay/resize_handle_button.h"
 #include "chrome/browser/ui/views/overlay/skip_ad_label_button.h"
+#include "chrome/browser/ui/views/overlay/track_image_button.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/vector_icons/vector_icons.h"
 #include "content/public/browser/picture_in_picture_window_controller.h"
@@ -128,7 +128,8 @@
          window->GetFirstCustomControlsBounds().Contains(point) ||
          window->GetSecondCustomControlsBounds().Contains(point) ||
          window->GetPlayPauseControlsBounds().Contains(point) ||
-         window->GetNextTrackControlsBounds().Contains(point))) {
+         window->GetNextTrackControlsBounds().Contains(point) ||
+         window->GetPreviousTrackControlsBounds().Contains(point))) {
       return window_component;
     }
 
@@ -202,7 +203,16 @@
       resize_handle_view_(new views::ResizeHandleButton(this)),
 #endif
       play_pause_controls_view_(new views::PlaybackImageButton(this)),
-      next_track_controls_view_(new views::NextTrackImageButton(this)),
+      next_track_controls_view_(new views::TrackImageButton(
+          this,
+          vector_icons::kMediaNextTrackIcon,
+          l10n_util::GetStringUTF16(
+              IDS_PICTURE_IN_PICTURE_NEXT_TRACK_CONTROL_ACCESSIBLE_TEXT))),
+      previous_track_controls_view_(new views::TrackImageButton(
+          this,
+          vector_icons::kMediaPreviousTrackIcon,
+          l10n_util::GetStringUTF16(
+              IDS_PICTURE_IN_PICTURE_PREVIOUS_TRACK_CONTROL_ACCESSIBLE_TEXT))),
       hide_controls_timer_(
           FROM_HERE,
           base::TimeDelta::FromMilliseconds(2500 /* 2.5 seconds */),
@@ -386,6 +396,9 @@
   // views::View that holds the next-track image button. ----------------------
   next_track_controls_view_->set_owned_by_client();
 
+  // views::View that holds the previous-track image button. ------------------
+  previous_track_controls_view_->set_owned_by_client();
+
 #if defined(OS_CHROMEOS)
   // views::View that shows the affordance that the window can be resized. ----
   resize_handle_view_->SetPaintToLayer(ui::LAYER_TEXTURED);
@@ -397,6 +410,7 @@
   // Set up view::Views hierarchy. --------------------------------------------
   controls_parent_view_->AddChildView(play_pause_controls_view_.get());
   controls_parent_view_->AddChildView(next_track_controls_view_.get());
+  controls_parent_view_->AddChildView(previous_track_controls_view_.get());
   GetContentsView()->AddChildView(controls_scrim_view_.get());
   GetContentsView()->AddChildView(controls_parent_view_.get());
   GetContentsView()->AddChildView(skip_ad_controls_view_.get());
@@ -442,6 +456,8 @@
                                         !always_hide_play_pause_button_);
   next_track_controls_view_->ToggleVisibility(is_visible &&
                                               show_next_track_button_);
+  previous_track_controls_view_->ToggleVisibility(is_visible &&
+                                                  show_previous_track_button_);
   GetControlsScrimLayer()->SetVisible(is_visible);
   GetControlsParentLayer()->SetVisible(is_visible);
   GetBackToTabControlsLayer()->SetVisible(is_visible);
@@ -476,13 +492,20 @@
       gfx::Rect(gfx::Point(0, 0), GetBounds().size()));
 
   // FIXME: Merge with UpdateControlsPositions when custom controls are removed.
-  if (show_next_track_button_) {
+  if (show_next_track_button_ || show_previous_track_button_) {
     int mid_window_x = GetBounds().size().width() / 2;
     play_pause_controls_view_->SetBoundsRect(CalculateControlsBounds(
         mid_window_x - button_size_.width() / 2, button_size_));
-    next_track_controls_view_->SetBoundsRect(CalculateControlsBounds(
-        mid_window_x + button_size_.width() / 2 + kControlButtonMargin,
-        next_track_controls_view_->GetLastVisibleSize()));
+    if (show_next_track_button_)
+      next_track_controls_view_->SetBoundsRect(CalculateControlsBounds(
+          mid_window_x + button_size_.width() / 2 + kControlButtonMargin,
+          next_track_controls_view_->GetLastVisibleSize()));
+    if (show_previous_track_button_)
+      previous_track_controls_view_->SetBoundsRect(CalculateControlsBounds(
+          mid_window_x - button_size_.width() / 2 -
+              previous_track_controls_view_->GetLastVisibleSize().width() -
+              kControlButtonMargin,
+          previous_track_controls_view_->GetLastVisibleSize()));
   } else {
     UpdateControlsPositions();
   }
@@ -534,6 +557,7 @@
   gfx::Size track_button_size =
       gfx::ScaleToRoundedSize(button_size_, kTrackButtonSizeScale);
   next_track_controls_view_->SetSize(track_button_size);
+  previous_track_controls_view_->SetSize(track_button_size);
 }
 
 void OverlayWindowViews::CreateCustomControl(
@@ -683,6 +707,14 @@
   UpdateControlsBounds();
 }
 
+void OverlayWindowViews::SetPreviousTrackButtonVisibility(bool is_visible) {
+  if (show_previous_track_button_ == is_visible)
+    return;
+
+  show_previous_track_button_ = is_visible;
+  UpdateControlsBounds();
+}
+
 void OverlayWindowViews::SetPictureInPictureCustomControls(
     const std::vector<blink::PictureInPictureControlInfo>& controls) {
   // Clear any existing controls.
@@ -871,6 +903,9 @@
   } else if (GetNextTrackControlsBounds().Contains(event->location())) {
     controller_->NextTrack();
     event->SetHandled();
+  } else if (GetPreviousTrackControlsBounds().Contains(event->location())) {
+    controller_->PreviousTrack();
+    event->SetHandled();
   }
 }
 
@@ -892,6 +927,9 @@
   if (sender == next_track_controls_view_.get())
     controller_->NextTrack();
 
+  if (sender == previous_track_controls_view_.get())
+    controller_->PreviousTrack();
+
   if (sender == first_custom_controls_view_.get())
     controller_->CustomControlPressed(first_custom_controls_view_->id());
 
@@ -923,6 +961,10 @@
   return next_track_controls_view_->GetMirroredBounds();
 }
 
+gfx::Rect OverlayWindowViews::GetPreviousTrackControlsBounds() {
+  return previous_track_controls_view_->GetMirroredBounds();
+}
+
 gfx::Rect OverlayWindowViews::GetFirstCustomControlsBounds() {
   if (!first_custom_controls_view_)
     return gfx::Rect();
@@ -976,11 +1018,16 @@
   return play_pause_controls_view_.get();
 }
 
-views::NextTrackImageButton*
+views::TrackImageButton*
 OverlayWindowViews::next_track_controls_view_for_testing() const {
   return next_track_controls_view_.get();
 }
 
+views::TrackImageButton*
+OverlayWindowViews::previous_track_controls_view_for_testing() const {
+  return previous_track_controls_view_.get();
+}
+
 gfx::Point OverlayWindowViews::back_to_tab_image_position_for_testing() const {
   return back_to_tab_controls_view_->origin();
 }
diff --git a/chrome/browser/ui/views/overlay/overlay_window_views.h b/chrome/browser/ui/views/overlay/overlay_window_views.h
index b305837..e0c8be0 100644
--- a/chrome/browser/ui/views/overlay/overlay_window_views.h
+++ b/chrome/browser/ui/views/overlay/overlay_window_views.h
@@ -20,10 +20,10 @@
 class BackToTabImageButton;
 class ControlImageButton;
 class CloseImageButton;
-class NextTrackImageButton;
 class PlaybackImageButton;
 class ResizeHandleButton;
 class SkipAdLabelButton;
+class TrackImageButton;
 }  // namespace views
 
 // The Chrome desktop implementation of OverlayWindow. This will only be
@@ -52,6 +52,7 @@
   void SetAlwaysHidePlayPauseButton(bool is_visible) override;
   void SetSkipAdButtonVisibility(bool is_visible) override;
   void SetNextTrackButtonVisibility(bool is_visible) override;
+  void SetPreviousTrackButtonVisibility(bool is_visible) override;
   void SetPictureInPictureCustomControls(
       const std::vector<blink::PictureInPictureControlInfo>& controls) override;
   ui::Layer* GetWindowBackgroundLayer() override;
@@ -80,6 +81,7 @@
   gfx::Rect GetResizeHandleControlsBounds();
   gfx::Rect GetPlayPauseControlsBounds();
   gfx::Rect GetNextTrackControlsBounds();
+  gfx::Rect GetPreviousTrackControlsBounds();
   gfx::Rect GetFirstCustomControlsBounds();
   gfx::Rect GetSecondCustomControlsBounds();
 
@@ -92,7 +94,8 @@
   bool AreControlsVisible() const;
 
   views::PlaybackImageButton* play_pause_controls_view_for_testing() const;
-  views::NextTrackImageButton* next_track_controls_view_for_testing() const;
+  views::TrackImageButton* next_track_controls_view_for_testing() const;
+  views::TrackImageButton* previous_track_controls_view_for_testing() const;
   gfx::Point back_to_tab_image_position_for_testing() const;
   views::SkipAdLabelButton* skip_ad_controls_view_for_testing() const;
   gfx::Point close_image_position_for_testing() const;
@@ -206,7 +209,8 @@
   std::unique_ptr<views::CloseImageButton> close_controls_view_;
   std::unique_ptr<views::ResizeHandleButton> resize_handle_view_;
   std::unique_ptr<views::PlaybackImageButton> play_pause_controls_view_;
-  std::unique_ptr<views::NextTrackImageButton> next_track_controls_view_;
+  std::unique_ptr<views::TrackImageButton> next_track_controls_view_;
+  std::unique_ptr<views::TrackImageButton> previous_track_controls_view_;
   std::unique_ptr<views::ControlImageButton> first_custom_controls_view_;
   std::unique_ptr<views::ControlImageButton> second_custom_controls_view_;
 #if defined(OS_CHROMEOS)
@@ -228,6 +232,10 @@
   // case when Media Session "nexttrack" action is handled by the website.
   bool show_next_track_button_ = false;
 
+  // Whether or not the previous track button will be shown. This is the
+  // case when Media Session "previoustrack" action is handled by the website.
+  bool show_previous_track_button_ = false;
+
   DISALLOW_COPY_AND_ASSIGN(OverlayWindowViews);
 };
 
diff --git a/chrome/browser/ui/views/overlay/resize_handle_button.h b/chrome/browser/ui/views/overlay/resize_handle_button.h
index b6b52d9..f37cf478 100644
--- a/chrome/browser/ui/views/overlay/resize_handle_button.h
+++ b/chrome/browser/ui/views/overlay/resize_handle_button.h
@@ -13,7 +13,7 @@
 // An image button representing a white resize handle affordance.
 class ResizeHandleButton : public views::ImageButton {
  public:
-  explicit ResizeHandleButton(ButtonListener* listener);
+  explicit ResizeHandleButton(ButtonListener*);
   ~ResizeHandleButton() override;
 
   void SetPosition(const gfx::Size& size,
diff --git a/chrome/browser/ui/views/overlay/skip_ad_label_button.h b/chrome/browser/ui/views/overlay/skip_ad_label_button.h
index 1b17b9a..91128c0 100644
--- a/chrome/browser/ui/views/overlay/skip_ad_label_button.h
+++ b/chrome/browser/ui/views/overlay/skip_ad_label_button.h
@@ -13,7 +13,7 @@
 // A label button representing a skip-ad button.
 class SkipAdLabelButton : public views::LabelButton {
  public:
-  explicit SkipAdLabelButton(ButtonListener* listener);
+  explicit SkipAdLabelButton(ButtonListener*);
   ~SkipAdLabelButton() override = default;
 
   // Sets the position of itself with an offset from the given window size.
diff --git a/chrome/browser/ui/views/overlay/track_image_button.cc b/chrome/browser/ui/views/overlay/track_image_button.cc
new file mode 100644
index 0000000..11ef735e
--- /dev/null
+++ b/chrome/browser/ui/views/overlay/track_image_button.cc
@@ -0,0 +1,57 @@
+// Copyright 2019 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 "chrome/browser/ui/views/overlay/track_image_button.h"
+
+#include "chrome/app/vector_icons/vector_icons.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/vector_icons/vector_icons.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/paint_vector_icon.h"
+#include "ui/views/vector_icons.h"
+
+namespace {
+
+SkColor kTrackIconColor = SK_ColorWHITE;
+
+}  // namespace
+
+namespace views {
+
+TrackImageButton::TrackImageButton(ButtonListener* listener,
+                                   const gfx::VectorIcon& icon,
+                                   base::string16 label)
+    : ImageButton(listener), icon_(icon) {
+  SetImageAlignment(views::ImageButton::ALIGN_CENTER,
+                    views::ImageButton::ALIGN_MIDDLE);
+
+  // Accessibility.
+  SetFocusForPlatform();
+  SetAccessibleName(label);
+  SetTooltipText(label);
+  SetInstallFocusRingOnFocus(true);
+}
+
+TrackImageButton::~TrackImageButton() = default;
+
+gfx::Size TrackImageButton::GetLastVisibleSize() const {
+  return size().IsEmpty() ? last_visible_size_ : size();
+}
+
+void TrackImageButton::OnBoundsChanged(const gfx::Rect&) {
+  SetImage(views::Button::STATE_NORMAL,
+           gfx::CreateVectorIcon(icon_, size().width() / 2, kTrackIconColor));
+}
+
+void TrackImageButton::ToggleVisibility(bool is_visible) {
+  if (is_visible && !size().IsEmpty())
+    last_visible_size_ = size();
+
+  SetVisible(is_visible);
+  SetEnabled(is_visible);
+  SetSize(is_visible ? GetLastVisibleSize() : gfx::Size());
+}
+
+}  // namespace views
diff --git a/chrome/browser/ui/views/overlay/track_image_button.h b/chrome/browser/ui/views/overlay/track_image_button.h
new file mode 100644
index 0000000..a67bc8a
--- /dev/null
+++ b/chrome/browser/ui/views/overlay/track_image_button.h
@@ -0,0 +1,46 @@
+// Copyright 2019 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 CHROME_BROWSER_UI_VIEWS_OVERLAY_TRACK_IMAGE_BUTTON_H_
+#define CHROME_BROWSER_UI_VIEWS_OVERLAY_TRACK_IMAGE_BUTTON_H_
+
+#include "chrome/browser/ui/views/overlay/overlay_window_views.h"
+#include "ui/views/controls/button/image_button.h"
+
+namespace gfx {
+struct VectorIcon;
+}
+
+namespace views {
+
+// A resizable previous/next track image button.
+class TrackImageButton : public views::ImageButton {
+ public:
+  explicit TrackImageButton(ButtonListener*,
+                            const gfx::VectorIcon& icon,
+                            base::string16 label);
+  ~TrackImageButton() override;
+
+  // Get button size when visible.
+  gfx::Size GetLastVisibleSize() const;
+
+  // Toggle visibility.
+  void ToggleVisibility(bool is_visible);
+
+ protected:
+  // Overridden from views::View.
+  void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
+
+ private:
+  const gfx::VectorIcon& icon_;
+
+  // Last visible size of the image button.
+  gfx::Size last_visible_size_;
+
+  DISALLOW_COPY_AND_ASSIGN(TrackImageButton);
+};
+
+}  // namespace views
+
+#endif  // CHROME_BROWSER_UI_VIEWS_OVERLAY_TRACK_IMAGE_BUTTON_H_
diff --git a/chrome/browser/ui/views/profiles/incognito_window_count_view.cc b/chrome/browser/ui/views/profiles/incognito_window_count_view.cc
index 6b4bdc9..48e04e0 100644
--- a/chrome/browser/ui/views/profiles/incognito_window_count_view.cc
+++ b/chrome/browser/ui/views/profiles/incognito_window_count_view.cc
@@ -82,7 +82,7 @@
   const SkColor icon_color = ThemeProperties::GetDefaultColor(
       ThemeProperties::COLOR_NTP_BACKGROUND, true /* incognito */);
   incognito_icon->SetImage(
-      gfx::CreateVectorIcon(kIncognitoIcon, 40, icon_color));
+      gfx::CreateVectorIcon(kIncognitoProfileIcon, icon_color));
 
   // TODO(https://crbug.com/915120): This Button is never clickable. Replace
   // by an alternative list item.
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view_browsertest.cc b/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view_browsertest.cc
index 47434a7..f076ecf 100644
--- a/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view_browsertest.cc
+++ b/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view_browsertest.cc
@@ -4,6 +4,7 @@
 
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
 #include "chrome/browser/browser_features.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
@@ -147,8 +148,14 @@
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
+// Fails on win7 (dbg): http://crbug.com/932402.
+#if defined(OS_WIN) && !defined(NDEBUG)
+#define MAYBE_InvokeUi_tab_hover_card DISABLED_InvokeUi_tab_hover_card
+#else
+#define MAYBE_InvokeUi_tab_hover_card InvokeUi_tab_hover_card
+#endif
 IN_PROC_BROWSER_TEST_F(TabHoverCardBubbleViewBrowserTest,
-                       InvokeUi_tab_hover_card) {
+                       MAYBE_InvokeUi_tab_hover_card) {
   ShowAndVerifyUi();
 }
 
diff --git a/chrome/browser/ui/webui/app_management/app_management_ui.cc b/chrome/browser/ui/webui/app_management/app_management_ui.cc
index 30350d68..f716c6d2 100644
--- a/chrome/browser/ui/webui/app_management/app_management_ui.cc
+++ b/chrome/browser/ui/webui/app_management/app_management_ui.cc
@@ -94,6 +94,10 @@
                           IDR_APP_MANAGEMENT_CHROME_APP_PERMISSION_VIEW_JS);
   source->AddResourcePath("constants.html", IDR_APP_MANAGEMENT_CONSTANTS_HTML);
   source->AddResourcePath("constants.js", IDR_APP_MANAGEMENT_CONSTANTS_JS);
+  source->AddResourcePath("expandable_app_list.html",
+                          IDR_APP_MANAGEMENT_EXPANDABLE_APP_LIST_HTML);
+  source->AddResourcePath("expandable_app_list.js",
+                          IDR_APP_MANAGEMENT_EXPANDABLE_APP_LIST_JS);
   source->AddResourcePath("dom_switch.html",
                           IDR_APP_MANAGEMENT_DOM_SWITCH_HTML);
   source->AddResourcePath("dom_switch.js", IDR_APP_MANAGEMENT_DOM_SWITCH_JS);
diff --git a/chrome/browser/ui/webui/settings/chromeos/crostini_handler.cc b/chrome/browser/ui/webui/settings/chromeos/crostini_handler.cc
index 69d507c0..df251be 100644
--- a/chrome/browser/ui/webui/settings/chromeos/crostini_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/crostini_handler.cc
@@ -6,6 +6,7 @@
 
 #include "base/bind.h"
 #include "base/bind_helpers.h"
+#include "chrome/browser/chromeos/crostini/crostini_export_import.h"
 #include "chrome/browser/chromeos/crostini/crostini_manager.h"
 #include "chrome/browser/chromeos/crostini/crostini_share_path.h"
 #include "chrome/browser/chromeos/crostini/crostini_util.h"
@@ -39,6 +40,14 @@
       "removeCrostiniSharedPath",
       base::BindRepeating(&CrostiniHandler::HandleRemoveCrostiniSharedPath,
                           weak_ptr_factory_.GetWeakPtr()));
+  web_ui()->RegisterMessageCallback(
+      "exportCrostiniContainer",
+      base::BindRepeating(&CrostiniHandler::HandleExportCrostiniContainer,
+                          weak_ptr_factory_.GetWeakPtr()));
+  web_ui()->RegisterMessageCallback(
+      "importCrostiniContainer",
+      base::BindRepeating(&CrostiniHandler::HandleImportCrostiniContainer,
+                          weak_ptr_factory_.GetWeakPtr()));
 }
 
 void CrostiniHandler::HandleRequestCrostiniInstallerView(
@@ -89,5 +98,19 @@
           path));
 }
 
+void CrostiniHandler::HandleExportCrostiniContainer(
+    const base::ListValue* args) {
+  CHECK_EQ(0U, args->GetSize());
+  crostini::CrostiniExportImport::GetForProfile(profile_)->ExportContainer(
+      web_ui()->GetWebContents());
+}
+
+void CrostiniHandler::HandleImportCrostiniContainer(
+    const base::ListValue* args) {
+  CHECK_EQ(0U, args->GetSize());
+  crostini::CrostiniExportImport::GetForProfile(profile_)->ImportContainer(
+      web_ui()->GetWebContents());
+}
+
 }  // namespace settings
 }  // namespace chromeos
diff --git a/chrome/browser/ui/webui/settings/chromeos/crostini_handler.h b/chrome/browser/ui/webui/settings/chromeos/crostini_handler.h
index 37b71f5..f0e6c0d1 100644
--- a/chrome/browser/ui/webui/settings/chromeos/crostini_handler.h
+++ b/chrome/browser/ui/webui/settings/chromeos/crostini_handler.h
@@ -36,6 +36,10 @@
   void HandleGetCrostiniSharedPathsDisplayText(const base::ListValue* args);
   // Remove a specified path from being shared.
   void HandleRemoveCrostiniSharedPath(const base::ListValue* args);
+  // Export the crostini container.
+  void HandleExportCrostiniContainer(const base::ListValue* args);
+  // Import the crostini container.
+  void HandleImportCrostiniContainer(const base::ListValue* args);
 
   Profile* profile_;
   // weak_ptr_factory_ should always be last member.
diff --git a/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
index 7291835..bc03ac0 100644
--- a/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
@@ -438,7 +438,8 @@
 }
 
 #if defined(OS_CHROMEOS)
-void AddCrostiniStrings(content::WebUIDataSource* html_source) {
+void AddCrostiniStrings(content::WebUIDataSource* html_source,
+                        Profile* profile) {
   static constexpr LocalizedString kLocalizedStrings[] = {
       {"crostiniPageTitle", IDS_SETTINGS_CROSTINI_TITLE},
       {"crostiniPageLabel", IDS_SETTINGS_CROSTINI_LABEL},
@@ -453,6 +454,11 @@
        IDS_SETTINGS_CROSTINI_SHARED_PATHS_INSTRUCTIONS_REMOVE},
       {"crostiniSharedPathsRemoveSharing",
        IDS_SETTINGS_CROSTINI_SHARED_PATHS_REMOVE_SHARING},
+      {"crostiniExportImportTitle", IDS_SETTINGS_CROSTINI_EXPORT_IMPORT_TITLE},
+      {"crostiniExport", IDS_SETTINGS_CROSTINI_EXPORT},
+      {"crostiniExportLabel", IDS_SETTINGS_CROSTINI_EXPORT_LABEL},
+      {"crostiniImport", IDS_SETTINGS_CROSTINI_IMPORT},
+      {"crostiniImportLabel", IDS_SETTINGS_CROSTINI_IMPORT_LABEL},
       {"crostiniSharedUsbDevicesLabel",
        IDS_SETTINGS_CROSTINI_SHARED_USB_DEVICES_LABEL},
       {"crostiniSharedUsbDevicesDescription",
@@ -472,6 +478,9 @@
           base::ASCIIToUTF16(
               crostini::ContainerChromeOSBaseDirectory().value())));
   html_source->AddBoolean(
+      "showCrostiniExportImport",
+      crostini::IsCrostiniExportImportUIAllowedForProfile(profile));
+  html_source->AddBoolean(
       "enableCrostiniUsbDeviceSupport",
       base::FeatureList::IsEnabled(chromeos::features::kCrostiniUsbSupport));
 }
@@ -2825,7 +2834,7 @@
   AddWebContentStrings(html_source);
 
 #if defined(OS_CHROMEOS)
-  AddCrostiniStrings(html_source);
+  AddCrostiniStrings(html_source, profile);
   AddContainedShellStrings(html_source);
   AddAndroidAppStrings(html_source);
   AddBluetoothStrings(html_source);
diff --git a/chrome/browser/ui/webui/signin/dice_turn_sync_on_helper.cc b/chrome/browser/ui/webui/signin/dice_turn_sync_on_helper.cc
index 5291a78..87f6502 100644
--- a/chrome/browser/ui/webui/signin/dice_turn_sync_on_helper.cc
+++ b/chrome/browser/ui/webui/signin/dice_turn_sync_on_helper.cc
@@ -33,7 +33,6 @@
 #include "components/prefs/pref_service.h"
 #include "components/signin/core/browser/account_consistency_method.h"
 #include "components/signin/core/browser/account_info.h"
-#include "components/signin/core/browser/device_id_helper.h"
 #include "components/signin/core/browser/signin_metrics.h"
 #include "components/signin/core/browser/signin_pref_names.h"
 #include "components/sync/base/sync_prefs.h"
@@ -413,11 +412,6 @@
       IdentityManagerFactory::GetForProfile(new_profile)->GetAccountsMutator();
   accounts_mutator->MoveAccount(new_profile_accounts_mutator,
                                 account_info_.account_id);
-  // Reset the device ID from the source profile: the exported token is linked
-  // to the device ID of the current profile on the server. Reset the device ID
-  // of the current profile to avoid tying it with the new profile. See
-  // https://crbug.com/813928#c16
-  signin::RecreateSigninScopedDeviceId(profile_->GetPrefs());
 
   SwitchToProfile(new_profile);
   DCHECK_EQ(profile_, new_profile);
diff --git a/chrome/browser/web_applications/components/web_app_shortcut_mac.mm b/chrome/browser/web_applications/components/web_app_shortcut_mac.mm
index cf5447d..e3ebf4c9 100644
--- a/chrome/browser/web_applications/components/web_app_shortcut_mac.mm
+++ b/chrome/browser/web_applications/components/web_app_shortcut_mac.mm
@@ -61,10 +61,10 @@
 #include "ui/gfx/image/image_family.h"
 
 // A TerminationObserver observes a NSRunningApplication for when it
-// terminates. On termination, it will run the specified callback, and then
-// release itself.
+// terminates. On termination, it will run the specified callback on the UI
+// thread and release itself.
 @interface TerminationObserver : NSObject {
-  NSRunningApplication* app_;
+  base::scoped_nsobject<NSRunningApplication> app_;
   base::OnceClosure callback_;
 }
 - (id)initWithRunningApplication:(NSRunningApplication*)app
@@ -75,25 +75,38 @@
 - (id)initWithRunningApplication:(NSRunningApplication*)app
                         callback:(base::OnceClosure)callback {
   if (self = [super init]) {
-    app_ = app;
     callback_ = std::move(callback);
-    [app_ retain];
+    app_.reset(app, base::scoped_policy::RETAIN);
+    // Note that |observeValueForKeyPath| will be called with the initial value
+    // within the |addObserver| call.
     [app_ addObserver:self
            forKeyPath:@"isTerminated"
-              options:NSKeyValueObservingOptionNew
+              options:NSKeyValueObservingOptionNew |
+                      NSKeyValueObservingOptionInitial
               context:nullptr];
   }
   return self;
 }
+
 - (void)observeValueForKeyPath:(NSString*)keyPath
                       ofObject:(id)object
                         change:(NSDictionary*)change
                        context:(void*)context {
+  NSNumber* newNumberValue = [change objectForKey:NSKeyValueChangeNewKey];
+  BOOL newValue = [newNumberValue boolValue];
+  if (newValue) {
+    base::PostTaskWithTraits(
+        FROM_HERE, {content::BrowserThread::UI},
+        base::BindOnce(
+            [](TerminationObserver* observer) { [observer onTerminated]; },
+            self));
+  }
+}
+
+- (void)onTerminated {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  std::move(callback_).Run();
   [app_ removeObserver:self forKeyPath:@"isTerminated" context:nullptr];
-  base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::UI},
-                           std::move(callback_));
-  [app_ release];
-  app_ = nil;
   [self release];
 }
 @end
diff --git a/chrome/installer/setup/setup_util_unittest.cc b/chrome/installer/setup/setup_util_unittest.cc
index 6f7c76b4..eaeb021 100644
--- a/chrome/installer/setup/setup_util_unittest.cc
+++ b/chrome/installer/setup/setup_util_unittest.cc
@@ -27,6 +27,7 @@
 #include "base/version.h"
 #include "base/win/registry.h"
 #include "base/win/scoped_handle.h"
+#include "build/build_config.h"
 #include "chrome/install_static/install_details.h"
 #include "chrome/install_static/install_util.h"
 #include "chrome/install_static/test/scoped_install_details.h"
@@ -192,7 +193,14 @@
 }  // namespace
 
 // Launching a subprocess at normal priority class is a noop.
-TEST(SetupUtilTest, AdjustFromNormalPriority) {
+// See crbug.com/930336: win-asap bots run unit tests using
+// BELOW_NORMAL_PRIORITY_CLASS (0x4000) which prevents running some tests.
+#if defined(OS_WIN) && defined(ADDRESS_SANITIZER)
+#define MAYBE_AdjustFromNormalPriority DISABLED_AdjustFromNormalPriority
+#else
+#define MAYBE_AdjustFromNormalPriority AdjustFromNormalPriority
+#endif
+TEST(SetupUtilTest, MAYBE_AdjustFromNormalPriority) {
   ASSERT_EQ(static_cast<DWORD>(NORMAL_PRIORITY_CLASS),
             ::GetPriorityClass(::GetCurrentProcess()));
   EXPECT_EQ(PCCR_UNCHANGED, RelaunchAndDoProcessPriorityAdjustment());
@@ -200,7 +208,15 @@
 
 // Launching a subprocess below normal priority class drops it to bg mode for
 // sufficiently recent operating systems.
-TEST(SetupUtilTest, AdjustFromBelowNormalPriority) {
+// See crbug.com/930336: win-asap bots run unit tests using
+// BELOW_NORMAL_PRIORITY_CLASS (0x4000) which prevents running some tests.
+#if defined(OS_WIN) && defined(ADDRESS_SANITIZER)
+#define MAYBE_AdjustFromBelowNormalPriority \
+  DISABLED_AdjustFromBelowNormalPriority
+#else
+#define MAYBE_AdjustFromBelowNormalPriority AdjustFromBelowNormalPriority
+#endif
+TEST(SetupUtilTest, MAYBE_AdjustFromBelowNormalPriority) {
   std::unique_ptr<ScopedPriorityClass> below_normal =
       ScopedPriorityClass::Create(BELOW_NORMAL_PRIORITY_CLASS);
   ASSERT_TRUE(below_normal);
diff --git a/chrome/test/data/chromeos/oobe_webui_browsertest.js b/chrome/test/data/chromeos/oobe_webui_browsertest.js
index 742f52c..de09a39 100644
--- a/chrome/test/data/chromeos/oobe_webui_browsertest.js
+++ b/chrome/test/data/chromeos/oobe_webui_browsertest.js
@@ -92,18 +92,6 @@
       'badAriaAttribute',
       badAriaAttributeSelectors);
 
-    var tabIndexGreaterThanZeroSelectors = [
-      '#user-image-grid',
-      '#discard-photo',
-      '#take-photo',
-    ];
-
-    // Enable when failure is resolved.
-    // AX_FOCUS_03: http://crbug.com/560928
-    this.accessibilityAuditConfig.ignoreSelectors(
-      'tabIndexGreaterThanZero',
-      tabIndexGreaterThanZeroSelectors);
-
     var controlsWithoutLabelSelectors = [
       '#supervised-user-creation-managers-pane',
       '#supervised-user-creation-name',
diff --git a/chrome/test/data/client_hints/accept_ch_with_lifetime.html.mock-http-headers b/chrome/test/data/client_hints/accept_ch_with_lifetime.html.mock-http-headers
index 6e22fe119..93ad1f1 100644
--- a/chrome/test/data/client_hints/accept_ch_with_lifetime.html.mock-http-headers
+++ b/chrome/test/data/client_hints/accept_ch_with_lifetime.html.mock-http-headers
@@ -1,3 +1,3 @@
 HTTP/1.1 200 OK
-Accept-CH: dpr,device-memory,viewport-width,rtt,downlink,ect,lang
+Accept-CH: dpr,device-memory,viewport-width,rtt,downlink,ect,lang,ua,arch,platform,model
 Accept-CH-Lifetime: 3600
diff --git a/chrome/test/data/client_hints/accept_ch_without_lifetime.html.mock-http-headers b/chrome/test/data/client_hints/accept_ch_without_lifetime.html.mock-http-headers
index 0234b8c..9c3216a7 100644
--- a/chrome/test/data/client_hints/accept_ch_without_lifetime.html.mock-http-headers
+++ b/chrome/test/data/client_hints/accept_ch_without_lifetime.html.mock-http-headers
@@ -1,2 +1,2 @@
 HTTP/1.1 200 OK
-Accept-CH: dpr,device-memory,viewport-width,rtt,downlink,ect,lang
+Accept-CH: dpr,device-memory,viewport-width,rtt,downlink,ect,lang,ua,arch,platform,model
diff --git a/chrome/test/data/client_hints/accept_ch_without_lifetime_img_localhost.html.mock-http-headers b/chrome/test/data/client_hints/accept_ch_without_lifetime_img_localhost.html.mock-http-headers
index 0234b8c..9c3216a7 100644
--- a/chrome/test/data/client_hints/accept_ch_without_lifetime_img_localhost.html.mock-http-headers
+++ b/chrome/test/data/client_hints/accept_ch_without_lifetime_img_localhost.html.mock-http-headers
@@ -1,2 +1,2 @@
 HTTP/1.1 200 OK
-Accept-CH: dpr,device-memory,viewport-width,rtt,downlink,ect,lang
+Accept-CH: dpr,device-memory,viewport-width,rtt,downlink,ect,lang,ua,arch,platform,model
diff --git a/chrome/test/data/client_hints/http_equiv_accept_ch_with_lifetime.html b/chrome/test/data/client_hints/http_equiv_accept_ch_with_lifetime.html
index d3c6d8cf..bdb172ad 100644
--- a/chrome/test/data/client_hints/http_equiv_accept_ch_with_lifetime.html
+++ b/chrome/test/data/client_hints/http_equiv_accept_ch_with_lifetime.html
@@ -1,5 +1,5 @@
 <html>
-<meta http-equiv="Accept-CH" content="dpr,device-memory,viewport-width,rtt,downlink,ect,lang">
+<meta http-equiv="Accept-CH" content="dpr,device-memory,viewport-width,rtt,downlink,ect,lang,ua,arch,platform,model">
 <meta http-equiv="Accept-CH-Lifetime" content="3600">
 <link rel="icon" href="data:;base64,=">
 </html>
diff --git a/chrome/test/data/client_hints/http_equiv_accept_ch_without_lifetime.html b/chrome/test/data/client_hints/http_equiv_accept_ch_without_lifetime.html
index 19f02f1..6d77646bd 100644
--- a/chrome/test/data/client_hints/http_equiv_accept_ch_without_lifetime.html
+++ b/chrome/test/data/client_hints/http_equiv_accept_ch_without_lifetime.html
@@ -1,5 +1,5 @@
 <html>
-<meta http-equiv="Accept-CH" content="dpr,device-memory,viewport-width,rtt,downlink,ect,lang">
+<meta http-equiv="Accept-CH" content="dpr,device-memory,viewport-width,rtt,downlink,ect,lang,ua,arch,platform,model">
 <link rel="icon" href="data:;base64,=">
 <head></head>
 Empty file which uses link-rel to disable favicon fetches. The corresponding
diff --git a/chrome/test/data/client_hints/http_equiv_accept_ch_without_lifetime_img_localhost.html b/chrome/test/data/client_hints/http_equiv_accept_ch_without_lifetime_img_localhost.html
index 02e61fa..9c7ae7b2 100644
--- a/chrome/test/data/client_hints/http_equiv_accept_ch_without_lifetime_img_localhost.html
+++ b/chrome/test/data/client_hints/http_equiv_accept_ch_without_lifetime_img_localhost.html
@@ -1,5 +1,5 @@
 <html>
-<meta http-equiv="Accept-CH" content="dpr,device-memory,viewport-width,rtt,downlink,ect,lang">
+<meta http-equiv="Accept-CH" content="dpr,device-memory,viewport-width,rtt,downlink,ect,lang,ua,arch,platform,model">
 <link rel="icon" href="data:;base64,=">
 <head></head>
 Empty file which uses link-rel to disable favicon fetches. The corresponding
diff --git a/chrome/test/data/webui/app_management/main_view_test.js b/chrome/test/data/webui/app_management/main_view_test.js
index f14d9c0..ddfde28f 100644
--- a/chrome/test/data/webui/app_management/main_view_test.js
+++ b/chrome/test/data/webui/app_management/main_view_test.js
@@ -31,12 +31,16 @@
   test('simple app addition', async function() {
     // Ensure there is no apps initially
     expectEquals(
-        0, mainView.root.querySelectorAll('app-management-app-item').length);
+        0,
+        mainView.$$('app-management-expandable-app-list')
+            .root.querySelectorAll('app-management-app-item')
+            .length);
 
     const appId = '1';
     await fakeHandler.addApp(appId);
 
-    let appItems = mainView.root.querySelectorAll('app-management-app-item');
+    let appItems = mainView.$$('app-management-expandable-app-list')
+                       .root.querySelectorAll('app-management-app-item');
     expectEquals(1, appItems.length);
 
     expectEquals(appId, appItems[0].app.id);
@@ -46,14 +50,24 @@
     // The more apps bar shouldn't appear when there are 4 apps.
     await addApps(4);
     expectEquals(
-        4, mainView.root.querySelectorAll('app-management-app-item').length);
-    expectTrue(mainView.$['expander-row'].hidden);
+        4,
+        mainView.$$('app-management-expandable-app-list')
+            .root.querySelectorAll('app-management-app-item')
+            .length);
+    expectTrue(mainView.$$('app-management-expandable-app-list')
+                   .$['expander-row']
+                   .hidden);
 
     // The more apps bar appears when there are 5 apps.
     await addApps(1);
     expectEquals(
-        5, mainView.root.querySelectorAll('app-management-app-item').length);
-    expectFalse(mainView.$['expander-row'].hidden);
+        5,
+        mainView.$$('app-management-expandable-app-list')
+            .root.querySelectorAll('app-management-app-item')
+            .length);
+    expectFalse(mainView.$$('app-management-expandable-app-list')
+                    .$['expander-row']
+                    .hidden);
   });
 
   test('notifications sublabel collapsibility', async function() {
diff --git a/chrome/test/data/webui/settings/crostini_page_test.js b/chrome/test/data/webui/settings/crostini_page_test.js
index 90897c2..98cb4733 100644
--- a/chrome/test/data/webui/settings/crostini_page_test.js
+++ b/chrome/test/data/webui/settings/crostini_page_test.js
@@ -80,6 +80,10 @@
 
     setup(function() {
       setCrostiniPrefs(true);
+      loadTimeData.overrideValues({
+        showCrostiniExportImport: true,
+      });
+
       settings.navigateTo(settings.routes.CROSTINI);
       crostiniPage.$$('#crostini').click();
       return flushAsync().then(() => {
@@ -90,6 +94,8 @@
 
     test('Sanity', function() {
       assertTrue(!!subpage.$$('#crostini-shared-paths'));
+      assertTrue(!!subpage.$$('#export'));
+      assertTrue(!!subpage.$$('#import'));
       assertTrue(!!subpage.$$('#remove'));
     });
 
@@ -102,6 +108,19 @@
       });
     });
 
+    test('Export', function() {
+      assertTrue(!!subpage.$$('#export paper-button'));
+      subpage.$$('#export paper-button').click();
+      assertEquals(
+          1, crostiniBrowserProxy.getCallCount('exportCrostiniContainer'));
+    });
+
+    test('Import', function() {
+      assertTrue(!!subpage.$$('#import paper-button'));
+      subpage.$$('#import paper-button').click();
+      assertEquals(
+          1, crostiniBrowserProxy.getCallCount('importCrostiniContainer'));
+    });
 
     test('Remove', function() {
       assertTrue(!!subpage.$$('#remove .subpage-arrow'));
diff --git a/chrome/test/data/webui/settings/test_crostini_browser_proxy.js b/chrome/test/data/webui/settings/test_crostini_browser_proxy.js
index 2fe2083..660b3a2 100644
--- a/chrome/test/data/webui/settings/test_crostini_browser_proxy.js
+++ b/chrome/test/data/webui/settings/test_crostini_browser_proxy.js
@@ -10,6 +10,8 @@
       'requestRemoveCrostini',
       'getCrostiniSharedPathsDisplayText',
       'removeCrostiniSharedPath',
+      'exportCrostiniContainer',
+      'importCrostiniContainer',
     ]);
     this.enabled = false;
     this.sharedPaths = ['path1', 'path2'];
@@ -37,4 +39,14 @@
   removeCrostiniSharedPath(path) {
     this.sharedPaths = this.sharedPaths.filter(p => p !== path);
   }
+
+  /** override */
+  exportCrostiniContainer() {
+    this.methodCalled('exportCrostiniContainer');
+  }
+
+  /** override */
+  importCrostiniContainer() {
+    this.methodCalled('importCrostiniContainer');
+  }
 }
diff --git a/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerUma.java b/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerUma.java
index 41efef4e..7979ddcd 100644
--- a/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerUma.java
+++ b/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerUma.java
@@ -36,8 +36,9 @@
     static final int BACKGROUND_TASK_DEPRECATED_EXPLORE_SITES_REFRESH = 16;
     static final int BACKGROUND_TASK_EXPLORE_SITES_REFRESH = 17;
     static final int BACKGROUND_TASK_DOWNLOAD_AUTO_RESUMPTION = 18;
+    static final int BACKGROUND_TASK_ONE_SHOT_SYNC_WAKE_UP = 19;
     // Keep this one at the end and increment appropriately when adding new tasks.
-    static final int BACKGROUND_TASK_COUNT = 19;
+    static final int BACKGROUND_TASK_COUNT = 20;
 
     static final String KEY_CACHED_UMA = "bts_cached_uma";
 
@@ -279,6 +280,8 @@
                 return BACKGROUND_TASK_DEPRECATED_EXPLORE_SITES_REFRESH;
             case TaskIds.EXPLORE_SITES_REFRESH_JOB_ID:
                 return BACKGROUND_TASK_EXPLORE_SITES_REFRESH;
+            case TaskIds.BACKGROUND_SYNC_ONE_SHOT_JOB_ID:
+                return BACKGROUND_TASK_ONE_SHOT_SYNC_WAKE_UP;
             default:
                 assert false;
         }
diff --git a/components/background_task_scheduler/android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerUmaTest.java b/components/background_task_scheduler/android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerUmaTest.java
index 905c63d..5f04f1cf 100644
--- a/components/background_task_scheduler/android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerUmaTest.java
+++ b/components/background_task_scheduler/android/junit/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerUmaTest.java
@@ -96,7 +96,10 @@
         assertEquals(BackgroundTaskSchedulerUma.BACKGROUND_TASK_DEPRECATED_EXPLORE_SITES_REFRESH,
                 BackgroundTaskSchedulerUma.toUmaEnumValueFromTaskId(
                         TaskIds.DEPRECATED_EXPLORE_SITES_REFRESH_JOB_ID));
-        assertEquals(BackgroundTaskSchedulerUma.BACKGROUND_TASK_COUNT, 19);
+        assertEquals(BackgroundTaskSchedulerUma.BACKGROUND_TASK_ONE_SHOT_SYNC_WAKE_UP,
+                BackgroundTaskSchedulerUma.toUmaEnumValueFromTaskId(
+                        TaskIds.BACKGROUND_SYNC_ONE_SHOT_JOB_ID));
+        assertEquals(BackgroundTaskSchedulerUma.BACKGROUND_TASK_COUNT, 20);
     }
 
     @Test
diff --git a/components/drive/chromeos/drive_test_util.h b/components/drive/chromeos/drive_test_util.h
index e2b12ea..c65fff11 100644
--- a/components/drive/chromeos/drive_test_util.h
+++ b/components/drive/chromeos/drive_test_util.h
@@ -12,17 +12,11 @@
 #include "components/drive/chromeos/file_cache.h"
 #include "content/public/test/test_utils.h"
 #include "google_apis/drive/test_util.h"
-#include "net/base/completion_callback.h"
 #include "net/base/io_buffer.h"
-#include "net/base/network_change_notifier.h"
 #include "net/base/test_completion_callback.h"
 
 class PrefRegistrySimple;
 
-namespace net {
-class IOBuffer;
-}  // namespace net
-
 namespace drive {
 
 namespace test_util {
diff --git a/components/password_manager/ios/resources/password_controller.js b/components/password_manager/ios/resources/password_controller.js
index 4ac09b0..7599dc1 100644
--- a/components/password_manager/ios/resources/password_controller.js
+++ b/components/password_manager/ios/resources/password_controller.js
@@ -210,7 +210,7 @@
  * @param {string} confirmPasswordIdentifier The id of confirm password element
  *   to fill.
  * @param {string} password The password to fill.
- * @return {boolean} Whether a password field has been filled.
+ * @return {boolean} Whether new password field has been filled.
 */
 __gCrWeb.passwords['fillPasswordFormWithGeneratedPassword'] = function(
     formName, newPasswordIdentifier, confirmPasswordIdentifier, password) {
@@ -220,15 +220,18 @@
   var inputs = getFormInputElements_(form);
   var newPasswordField =
       findInputByFieldIdentifier_(inputs, newPasswordIdentifier);
-  if (newPasswordField) {
-    newPasswordField.value = password;
+  if (!newPasswordField)
+    return false;
+  // Avoid resetting if same value, as it moves cursor to the end.
+  if (newPasswordField.value != password) {
+    __gCrWeb.fill.setInputElementValue(password, newPasswordField);
   }
   var confirmPasswordField =
       findInputByFieldIdentifier_(inputs, confirmPasswordIdentifier);
-  if (confirmPasswordField) {
-    confirmPasswordField.value = password;
+  if (confirmPasswordField && confirmPasswordField.value != password) {
+    __gCrWeb.fill.setInputElementValue(password, confirmPasswordField);
   }
-  return !!newPasswordField || !!confirmPasswordField;
+  return true;
 };
 
 /**
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index 3a2c2750..b60104c4 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -5847,7 +5847,7 @@
 
       Note that it is not recommended to block internal 'chrome://*' URLs since this may lead to unexpected errors.
 
-      Note that this policy does not prevent the page updating dynamically through JavaScript. For example, if you block 'example.com/abc', users might still be able to visit 'example.com' and click on a link to visit 'example.com/abc', as long as the page does not refresh.
+      From M73 you can block 'javascript://*' URLs. However, it affects only JavaScript typed in address bar (or, for example, bookmarklets). Note that in-page JavaScript URLs, as long as dynamically loaded data, are not subject to this policy. For example, if you block 'example.com/abc', page 'example.com' will still be able to load 'example.com/abc' via XMLHTTPRequest.
 
       If this policy is not set no URL will be blacklisted in the browser.''',
       'arc_support': 'Android apps may voluntarily choose to honor this list. You cannot force them to honor it.',
diff --git a/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.h b/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.h
index ae9c2d5..8815bc9 100644
--- a/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.h
+++ b/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.h
@@ -49,6 +49,8 @@
   // Subsequent calls to |RefreshTokenIsAvailable| will return |false|.
   void RevokeAllCredentials() override;
 
+  void AddAccountFromSystem(const std::string& account_id) override;
+
   void ReloadAccountsFromSystem(const std::string& primary_account_id) override;
 
   // Reloads accounts from the provider. Fires |OnRefreshTokenAvailable| for
diff --git a/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.mm b/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.mm
index 9ee4a20a..d931066f 100644
--- a/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.mm
+++ b/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.mm
@@ -270,6 +270,11 @@
   ClearExcludedSecondaryAccounts();
 }
 
+void ProfileOAuth2TokenServiceIOSDelegate::AddAccountFromSystem(
+    const std::string& account_id) {
+  AddOrUpdateAccount(account_id);
+}
+
 void ProfileOAuth2TokenServiceIOSDelegate::ReloadAccountsFromSystem(
     const std::string& primary_account_id) {
   if (primary_account_id.empty())
diff --git a/components/viz/common/frame_sinks/begin_frame_args.cc b/components/viz/common/frame_sinks/begin_frame_args.cc
index 3366378..0466d99 100644
--- a/components/viz/common/frame_sinks/begin_frame_args.cc
+++ b/components/viz/common/frame_sinks/begin_frame_args.cc
@@ -98,20 +98,6 @@
   state->SetBoolean("animate_only", animate_only);
 }
 
-// This is a hard-coded deadline adjustment that assumes 60Hz, to be used in
-// cases where a good estimated draw time is not known. Using 1/3 of the vsync
-// as the default adjustment gives the Browser the last 1/3 of a frame to
-// produce output, the Renderer Impl thread the middle 1/3 of a frame to produce
-// ouput, and the Renderer Main thread the first 1/3 of a frame to produce
-// output.
-base::TimeDelta BeginFrameArgs::DefaultEstimatedParentDrawTime() {
-  return base::TimeDelta::FromMicroseconds(16666 / 3);
-}
-
-base::TimeDelta BeginFrameArgs::DefaultInterval() {
-  return base::TimeDelta::FromMicroseconds(16666);
-}
-
 BeginFrameAck::BeginFrameAck()
     : source_id(0),
       sequence_number(BeginFrameArgs::kInvalidFrameNumber),
diff --git a/components/viz/common/frame_sinks/begin_frame_args.h b/components/viz/common/frame_sinks/begin_frame_args.h
index 6364247..faf8491 100644
--- a/components/viz/common/frame_sinks/begin_frame_args.h
+++ b/components/viz/common/frame_sinks/begin_frame_args.h
@@ -80,13 +80,21 @@
                                base::TimeDelta interval,
                                BeginFrameArgsType type);
 
-  // This is the default delta that will be used to adjust the deadline when
-  // proper draw-time estimations are not yet available.
-  static base::TimeDelta DefaultEstimatedParentDrawTime();
+  // This is the default interval assuming 60Hz to use to avoid sprinkling the
+  // code with magic numbers.
+  static constexpr base::TimeDelta DefaultInterval() {
+    return base::TimeDelta::FromMicroseconds(16666);
+  }
 
-  // This is the default interval to use to avoid sprinkling the code with
-  // magic numbers.
-  static base::TimeDelta DefaultInterval();
+  // This is a hard-coded deadline adjustment that assumes 60Hz, to be used in
+  // cases where a good estimated draw time is not known. Using 1/3 of the vsync
+  // as the default adjustment gives the Browser the last 1/3 of a frame to
+  // produce output, the Renderer Impl thread the middle 1/3 of a frame to
+  // produce ouput, and the Renderer Main thread the first 1/3 of a frame to
+  // produce output.
+  static constexpr base::TimeDelta DefaultEstimatedParentDrawTime() {
+    return base::TimeDelta::FromMicroseconds(16666 / 3);
+  }
 
   bool IsValid() const { return interval >= base::TimeDelta(); }
 
diff --git a/components/viz/service/display_embedder/gpu_display_provider.cc b/components/viz/service/display_embedder/gpu_display_provider.cc
index 53053fc..22578e2 100644
--- a/components/viz/service/display_embedder/gpu_display_provider.cc
+++ b/components/viz/service/display_embedder/gpu_display_provider.cc
@@ -122,7 +122,8 @@
 
   if (!gpu_compositing) {
     output_surface = std::make_unique<SoftwareOutputSurface>(
-        CreateSoftwareOutputDeviceForPlatform(surface_handle, display_client));
+        CreateSoftwareOutputDeviceForPlatform(surface_handle, display_client),
+        synthetic_begin_frame_source);
   } else if (renderer_settings.use_skia_renderer) {
 #if defined(OS_MACOSX) || defined(OS_WIN)
     // TODO(penghuang): Support DDL for all platforms.
diff --git a/components/viz/service/display_embedder/software_output_device_win.cc b/components/viz/service/display_embedder/software_output_device_win.cc
index 16b946d..a339eaa 100644
--- a/components/viz/service/display_embedder/software_output_device_win.cc
+++ b/components/viz/service/display_embedder/software_output_device_win.cc
@@ -19,6 +19,7 @@
 #include "ui/gfx/gdi_util.h"
 #include "ui/gfx/skia_util.h"
 #include "ui/gfx/win/hwnd_util.h"
+#include "ui/gl/vsync_provider_win.h"
 
 namespace viz {
 namespace {
@@ -26,7 +27,10 @@
 // Shared base class for Windows SoftwareOutputDevice implementations.
 class SoftwareOutputDeviceWinBase : public SoftwareOutputDevice {
  public:
-  explicit SoftwareOutputDeviceWinBase(HWND hwnd) : hwnd_(hwnd) {}
+  explicit SoftwareOutputDeviceWinBase(HWND hwnd) : hwnd_(hwnd) {
+    vsync_provider_ = std::make_unique<gl::VSyncProviderWin>(hwnd);
+  }
+
   ~SoftwareOutputDeviceWinBase() override {
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
     DCHECK(!in_paint_);
diff --git a/components/viz/service/display_embedder/software_output_surface.cc b/components/viz/service/display_embedder/software_output_surface.cc
index e48ab54..fcb4c75 100644
--- a/components/viz/service/display_embedder/software_output_surface.cc
+++ b/components/viz/service/display_embedder/software_output_surface.cc
@@ -11,6 +11,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
+#include "components/viz/common/frame_sinks/begin_frame_source.h"
 #include "components/viz/service/display/output_surface_client.h"
 #include "components/viz/service/display/output_surface_frame.h"
 #include "components/viz/service/display/software_output_device.h"
@@ -21,8 +22,11 @@
 namespace viz {
 
 SoftwareOutputSurface::SoftwareOutputSurface(
-    std::unique_ptr<SoftwareOutputDevice> software_device)
-    : OutputSurface(std::move(software_device)), weak_factory_(this) {}
+    std::unique_ptr<SoftwareOutputDevice> software_device,
+    SyntheticBeginFrameSource* synthetic_begin_frame_source)
+    : OutputSurface(std::move(software_device)),
+      synthetic_begin_frame_source_(synthetic_begin_frame_source),
+      weak_factory_(this) {}
 
 SoftwareOutputSurface::~SoftwareOutputSurface() = default;
 
@@ -72,14 +76,15 @@
       << "arrive before the previous latency info is processed.";
   stored_latency_info_ = std::move(frame.latency_info);
 
-  // TODO(danakj): Update vsync params.
-  // gfx::VSyncProvider* vsync_provider = software_device()->GetVSyncProvider();
-  // if (vsync_provider)
-  //  vsync_provider->GetVSyncParameters(update_vsync_parameters_callback_);
-  // Update refresh_interval_ as well.
-
   software_device()->OnSwapBuffers(base::BindOnce(
       &SoftwareOutputSurface::SwapBuffersCallback, weak_factory_.GetWeakPtr()));
+
+  gfx::VSyncProvider* vsync_provider = software_device()->GetVSyncProvider();
+  if (vsync_provider && synthetic_begin_frame_source_) {
+    vsync_provider->GetVSyncParameters(
+        base::BindOnce(&SoftwareOutputSurface::UpdateVSyncParametersCallback,
+                       weak_factory_.GetWeakPtr()));
+  }
 }
 
 bool SoftwareOutputSurface::IsDisplayedAsOverlayPlane() const {
@@ -117,8 +122,22 @@
   client_->DidFinishLatencyInfo(stored_latency_info_);
   std::vector<ui::LatencyInfo>().swap(stored_latency_info_);
   client_->DidReceiveSwapBuffersAck();
+
+  base::TimeTicks now = base::TimeTicks::Now();
+  base::TimeDelta interval_to_next_refresh =
+      now.SnappedToNextTick(refresh_timebase_, refresh_interval_) - now;
+
   client_->DidReceivePresentationFeedback(
-      gfx::PresentationFeedback(base::TimeTicks::Now(), refresh_interval_, 0u));
+      gfx::PresentationFeedback(now, interval_to_next_refresh, 0u));
+}
+
+void SoftwareOutputSurface::UpdateVSyncParametersCallback(
+    base::TimeTicks timebase,
+    base::TimeDelta interval) {
+  DCHECK(synthetic_begin_frame_source_);
+  refresh_timebase_ = timebase;
+  refresh_interval_ = interval;
+  synthetic_begin_frame_source_->OnUpdateVSyncParameters(timebase, interval);
 }
 
 #if BUILDFLAG(ENABLE_VULKAN)
diff --git a/components/viz/service/display_embedder/software_output_surface.h b/components/viz/service/display_embedder/software_output_surface.h
index c371a846..95c032b2 100644
--- a/components/viz/service/display_embedder/software_output_surface.h
+++ b/components/viz/service/display_embedder/software_output_surface.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_SOFTWARE_OUTPUT_SURFACE_H_
 
 #include "base/memory/weak_ptr.h"
+#include "components/viz/common/frame_sinks/begin_frame_args.h"
 #include "components/viz/service/display/output_surface.h"
 #include "components/viz/service/viz_service_export.h"
 #include "ui/latency/latency_info.h"
@@ -13,11 +14,13 @@
 
 namespace viz {
 class SoftwareOutputDevice;
+class SyntheticBeginFrameSource;
 
 class VIZ_SERVICE_EXPORT SoftwareOutputSurface : public OutputSurface {
  public:
-  explicit SoftwareOutputSurface(
-      std::unique_ptr<SoftwareOutputDevice> software_device);
+  SoftwareOutputSurface(
+      std::unique_ptr<SoftwareOutputDevice> software_device,
+      SyntheticBeginFrameSource* synthetic_begin_frame_source);
   ~SoftwareOutputSurface() override;
 
   // OutputSurface implementation.
@@ -46,11 +49,18 @@
 
  private:
   void SwapBuffersCallback();
+  void UpdateVSyncParametersCallback(base::TimeTicks timebase,
+                                     base::TimeDelta interval);
 
   OutputSurfaceClient* client_ = nullptr;
-  base::TimeDelta refresh_interval_;
+
+  SyntheticBeginFrameSource* const synthetic_begin_frame_source_;
+  base::TimeTicks refresh_timebase_;
+  base::TimeDelta refresh_interval_ = BeginFrameArgs::DefaultInterval();
+
   std::vector<ui::LatencyInfo> stored_latency_info_;
   ui::LatencyTracker latency_tracker_;
+
   base::WeakPtrFactory<SoftwareOutputSurface> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(SoftwareOutputSurface);
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index db9dae3e..c03305f 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -316,6 +316,8 @@
     "after_startup_task_utils.h",
     "android/android_overlay_provider_impl.cc",
     "android/android_overlay_provider_impl.h",
+    "android/android_ui_constants.cc",
+    "android/android_ui_constants.h",
     "android/app_web_message_port.cc",
     "android/app_web_message_port.h",
     "android/background_sync_network_observer_android.cc",
diff --git a/content/browser/android/android_ui_constants.cc b/content/browser/android/android_ui_constants.cc
new file mode 100644
index 0000000..f62c87d
--- /dev/null
+++ b/content/browser/android/android_ui_constants.cc
@@ -0,0 +1,27 @@
+// Copyright 2019 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/browser/android/android_ui_constants.h"
+
+#include "base/android/jni_android.h"
+#include "jni/UiConstants_jni.h"
+
+bool AndroidUiConstants::IsFocusRingOutset() {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  return Java_UiConstants_isFocusRingOutset(env);
+}
+
+base::Optional<float> AndroidUiConstants::GetMinimumStrokeWidthForFocusRing() {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  if (!Java_UiConstants_hasCustomMinimumStrokeWidthForFocusRing(env))
+    return base::nullopt;
+  return Java_UiConstants_getMinimumStrokeWidthForFocusRing(env);
+}
+
+base::Optional<SkColor> AndroidUiConstants::GetFocusRingColor() {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  if (!Java_UiConstants_hasCustomFocusRingColor(env))
+    return base::nullopt;
+  return Java_UiConstants_getFocusRingColor(env);
+}
diff --git a/content/browser/android/android_ui_constants.h b/content/browser/android/android_ui_constants.h
new file mode 100644
index 0000000..735f67a
--- /dev/null
+++ b/content/browser/android/android_ui_constants.h
@@ -0,0 +1,22 @@
+// Copyright 2019 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_BROWSER_ANDROID_ANDROID_UI_CONSTANTS_H_
+#define CONTENT_BROWSER_ANDROID_ANDROID_UI_CONSTANTS_H_
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "third_party/skia/include/core/SkColor.h"
+
+class AndroidUiConstants {
+ public:
+  static bool IsFocusRingOutset();
+  static base::Optional<float> GetMinimumStrokeWidthForFocusRing();
+  static base::Optional<SkColor> GetFocusRingColor();
+
+ private:
+  DISALLOW_IMPLICIT_CONSTRUCTORS(AndroidUiConstants);
+};
+
+#endif  // CONTENT_BROWSER_ANDROID_ANDROID_UI_CONSTANTS_H_
diff --git a/content/browser/child_process_security_policy_impl.cc b/content/browser/child_process_security_policy_impl.cc
index 5169f88..46813b6 100644
--- a/content/browser/child_process_security_policy_impl.cc
+++ b/content/browser/child_process_security_policy_impl.cc
@@ -34,6 +34,7 @@
 #include "content/public/common/bindings_policy.h"
 #include "content/public/common/url_constants.h"
 #include "net/base/filename_util.h"
+#include "net/base/url_util.h"
 #include "net/url_request/url_request.h"
 #include "services/network/public/cpp/resource_request_body.h"
 #include "storage/browser/fileapi/file_permission_policy.h"
@@ -1207,10 +1208,7 @@
     int child_id,
     const GURL& url) {
   DCHECK(url.SchemeIsWSOrWSS());
-  GURL::Replacements replace_scheme;
-  replace_scheme.SetSchemeStr(url.SchemeIs(url::kWssScheme) ? url::kHttpsScheme
-                                                            : url::kHttpScheme);
-  GURL url_to_check = url.ReplaceComponents(replace_scheme);
+  GURL url_to_check = net::ChangeWebSocketSchemeToHttpScheme(url);
   return CanAccessDataForOrigin(child_id, url_to_check);
 }
 
diff --git a/content/browser/compositor/software_browser_compositor_output_surface_unittest.cc b/content/browser/compositor/software_browser_compositor_output_surface_unittest.cc
index e12fab7..5ac8128f 100644
--- a/content/browser/compositor/software_browser_compositor_output_surface_unittest.cc
+++ b/content/browser/compositor/software_browser_compositor_output_surface_unittest.cc
@@ -26,8 +26,8 @@
   FakeVSyncProvider() : call_count_(0) {}
   ~FakeVSyncProvider() override {}
 
-  void GetVSyncParameters(const UpdateVSyncCallback& callback) override {
-    callback.Run(timebase_, interval_);
+  void GetVSyncParameters(UpdateVSyncCallback callback) override {
+    std::move(callback).Run(timebase_, interval_);
     call_count_++;
   }
 
diff --git a/content/browser/cookie_store/cookie_store_manager_unittest.cc b/content/browser/cookie_store/cookie_store_manager_unittest.cc
index 5981dd12..59c18ff 100644
--- a/content/browser/cookie_store/cookie_store_manager_unittest.cc
+++ b/content/browser/cookie_store/cookie_store_manager_unittest.cc
@@ -11,6 +11,8 @@
 #include "content/browser/cookie_store/cookie_store_context.h"
 #include "content/browser/cookie_store/cookie_store_manager.h"
 #include "content/browser/service_worker/embedded_worker_test_helper.h"
+#include "content/browser/service_worker/fake_embedded_worker_instance_client.h"
+#include "content/browser/service_worker/fake_service_worker.h"
 #include "content/browser/service_worker/service_worker_context_wrapper.h"
 #include "content/browser/storage_partition_impl.h"
 #include "content/public/test/test_browser_context.h"
@@ -86,6 +88,94 @@
  public:
   using EmbeddedWorkerTestHelper::EmbeddedWorkerTestHelper;
 
+  explicit CookieStoreWorkerTestHelper(
+      const base::FilePath& user_data_directory)
+      : EmbeddedWorkerTestHelper(user_data_directory) {}
+  ~CookieStoreWorkerTestHelper() override = default;
+
+  class EmbeddedWorkerInstanceClient : public FakeEmbeddedWorkerInstanceClient {
+   public:
+    explicit EmbeddedWorkerInstanceClient(
+        CookieStoreWorkerTestHelper* worker_helper)
+        : FakeEmbeddedWorkerInstanceClient(worker_helper),
+          worker_helper_(worker_helper) {}
+    ~EmbeddedWorkerInstanceClient() override = default;
+
+    // Collects the worker's registration ID for OnInstallEvent().
+    void StartWorker(
+        blink::mojom::EmbeddedWorkerStartParamsPtr params) override {
+      ServiceWorkerVersion* service_worker_version =
+          worker_helper_->context()->GetLiveVersion(
+              params->service_worker_version_id);
+      DCHECK(service_worker_version);
+      worker_helper_->service_worker_registration_id_ =
+          service_worker_version->registration_id();
+
+      FakeEmbeddedWorkerInstanceClient::StartWorker(std::move(params));
+    }
+
+   private:
+    CookieStoreWorkerTestHelper* const worker_helper_;
+
+    DISALLOW_COPY_AND_ASSIGN(EmbeddedWorkerInstanceClient);
+  };
+
+  class ServiceWorker : public FakeServiceWorker {
+   public:
+    explicit ServiceWorker(CookieStoreWorkerTestHelper* worker_helper)
+        : FakeServiceWorker(worker_helper), worker_helper_(worker_helper) {}
+    ~ServiceWorker() override = default;
+
+    // Cookie change subscriptions can only be created in this event handler.
+    void DispatchInstallEvent(DispatchInstallEventCallback callback) override {
+      for (auto& subscriptions :
+           worker_helper_->install_subscription_batches_) {
+        worker_helper_->cookie_store_service_->AppendSubscriptions(
+            worker_helper_->service_worker_registration_id_,
+            std::move(subscriptions), base::BindOnce([](bool success) {
+              CHECK(success) << "AppendSubscriptions failed";
+            }));
+      }
+      worker_helper_->install_subscription_batches_.clear();
+
+      FakeServiceWorker::DispatchInstallEvent(std::move(callback));
+    }
+
+    // Used to implement WaitForActivateEvent().
+    void DispatchActivateEvent(
+        DispatchActivateEventCallback callback) override {
+      if (worker_helper_->quit_on_activate_) {
+        worker_helper_->quit_on_activate_->Quit();
+        worker_helper_->quit_on_activate_ = nullptr;
+      }
+
+      FakeServiceWorker::DispatchActivateEvent(std::move(callback));
+    }
+
+    void DispatchCookieChangeEvent(
+        const net::CanonicalCookie& cookie,
+        ::network::mojom::CookieChangeCause cause,
+        DispatchCookieChangeEventCallback callback) override {
+      worker_helper_->changes_.emplace_back(cookie, cause);
+      std::move(callback).Run(
+          blink::mojom::ServiceWorkerEventStatus::COMPLETED);
+    }
+
+   private:
+    CookieStoreWorkerTestHelper* const worker_helper_;
+
+    DISALLOW_COPY_AND_ASSIGN(ServiceWorker);
+  };
+
+  std::unique_ptr<FakeEmbeddedWorkerInstanceClient> CreateInstanceClient()
+      override {
+    return std::make_unique<EmbeddedWorkerInstanceClient>(this);
+  }
+
+  std::unique_ptr<FakeServiceWorker> CreateServiceWorker() override {
+    return std::make_unique<ServiceWorker>(this);
+  }
+
   // Sets the cookie change subscriptions requested in the next install event.
   void SetOnInstallSubscriptions(
       std::vector<CookieStoreSync::Subscriptions> subscription_batches,
@@ -108,68 +198,6 @@
     return changes_;
   }
 
- protected:
-  // Collects the worker's registration ID for OnInstallEvent().
-  void OnStartWorker(
-      int embedded_worker_id,
-      int64_t service_worker_version_id,
-      const GURL& scope,
-      const GURL& script_url,
-      bool pause_after_download,
-      blink::mojom::ServiceWorkerRequest service_worker_request,
-      blink::mojom::ControllerServiceWorkerRequest controller_request,
-      blink::mojom::EmbeddedWorkerInstanceHostAssociatedPtrInfo instance_host,
-      blink::mojom::ServiceWorkerProviderInfoForStartWorkerPtr provider_info,
-      blink::mojom::ServiceWorkerInstalledScriptsInfoPtr installed_scripts_info)
-      override {
-    ServiceWorkerVersion* service_worker_version =
-        context()->GetLiveVersion(service_worker_version_id);
-    DCHECK(service_worker_version);
-    service_worker_registration_id_ = service_worker_version->registration_id();
-
-    EmbeddedWorkerTestHelper::OnStartWorker(
-        embedded_worker_id, service_worker_version_id, scope, script_url,
-        pause_after_download, std::move(service_worker_request),
-        std::move(controller_request), std::move(instance_host),
-        std::move(provider_info), std::move(installed_scripts_info));
-  }
-
-  // Cookie change subscriptions can only be created in this event handler.
-  void OnInstallEvent(blink::mojom::ServiceWorker::DispatchInstallEventCallback
-                          callback) override {
-    for (auto& subscriptions : install_subscription_batches_) {
-      cookie_store_service_->AppendSubscriptions(
-          service_worker_registration_id_, std::move(subscriptions),
-          base::BindOnce([](bool success) {
-            CHECK(success) << "AppendSubscriptions failed";
-          }));
-    }
-    install_subscription_batches_.clear();
-
-    EmbeddedWorkerTestHelper::OnInstallEvent(std::move(callback));
-  }
-
-  // Used to implement WaitForActivateEvent().
-  void OnActivateEvent(
-      blink::mojom::ServiceWorker::DispatchActivateEventCallback callback)
-      override {
-    if (quit_on_activate_) {
-      quit_on_activate_->Quit();
-      quit_on_activate_ = nullptr;
-    }
-
-    EmbeddedWorkerTestHelper::OnActivateEvent(std::move(callback));
-  }
-
-  void OnCookieChangeEvent(
-      const net::CanonicalCookie& cookie,
-      ::network::mojom::CookieChangeCause cause,
-      blink::mojom::ServiceWorker::DispatchCookieChangeEventCallback callback)
-      override {
-    changes_.emplace_back(cookie, cause);
-    std::move(callback).Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED);
-  }
-
  private:
   // Used to add cookie change subscriptions during OnInstallEvent().
   blink::mojom::CookieStore* cookie_store_service_ = nullptr;
diff --git a/content/browser/devtools/browser_devtools_agent_host.cc b/content/browser/devtools/browser_devtools_agent_host.cc
index 362a62ed..2862801 100644
--- a/content/browser/devtools/browser_devtools_agent_host.cc
+++ b/content/browser/devtools/browser_devtools_agent_host.cc
@@ -87,8 +87,8 @@
     session->AddHandler(std::make_unique<protocol::TetheringHandler>(
         socket_callback_, tethering_task_runner_));
   }
-  session->AddHandler(
-      std::make_unique<protocol::TracingHandler>(nullptr, GetIOContext()));
+  session->AddHandler(std::make_unique<protocol::TracingHandler>(
+      nullptr, GetIOContext(), session->client()->UsesBinaryProtocol()));
   return true;
 }
 
diff --git a/content/browser/devtools/devtools_session.cc b/content/browser/devtools/devtools_session.cc
index adf9a4f..990a9bdf7 100644
--- a/content/browser/devtools/devtools_session.cc
+++ b/content/browser/devtools/devtools_session.cc
@@ -336,10 +336,10 @@
   std::string patched;
   bool patched_ok;
   if (client_->UsesBinaryProtocol()) {
-    patched_ok = protocol::AppendStringValueToMapBinary(message, "sessionId",
+    patched_ok = protocol::AppendStringValueToMapBinary(message, kSessionId,
                                                         session_id, &patched);
   } else {
-    patched_ok = protocol::AppendStringValueToMapJSON(message, "sessionId",
+    patched_ok = protocol::AppendStringValueToMapJSON(message, kSessionId,
                                                       session_id, &patched);
   }
   if (!patched_ok)
diff --git a/content/browser/devtools/protocol/target_handler.cc b/content/browser/devtools/protocol/target_handler.cc
index b62b5892..bc31d2c 100644
--- a/content/browser/devtools/protocol/target_handler.cc
+++ b/content/browser/devtools/protocol/target_handler.cc
@@ -331,6 +331,11 @@
     return agent_host_->GetId() == target_id;
   }
 
+  bool UsesBinaryProtocol() override {
+    auto* client = handler_->root_session_->client();
+    return client->UsesBinaryProtocol();
+  }
+
  private:
   friend class TargetHandler;
 
diff --git a/content/browser/devtools/protocol/tracing_handler.cc b/content/browser/devtools/protocol/tracing_handler.cc
index 6609d738..fbca49f6 100644
--- a/content/browser/devtools/protocol/tracing_handler.cc
+++ b/content/browser/devtools/protocol/tracing_handler.cc
@@ -211,8 +211,10 @@
 }  // namespace
 
 TracingHandler::TracingHandler(FrameTreeNode* frame_tree_node_,
-                               DevToolsIOContext* io_context)
+                               DevToolsIOContext* io_context,
+                               bool use_binary_protocol)
     : DevToolsDomainHandler(Tracing::Metainfo::domainName),
+      use_binary_protocol_(use_binary_protocol),
       io_context_(io_context),
       frame_tree_node_(frame_tree_node_),
       did_initiate_recording_(false),
@@ -276,7 +278,12 @@
   message.append(valid_trace_fragment.c_str() +
                  trace_data_buffer_state_.offset);
   message += "] } }";
-  frontend_->sendRawNotification(std::move(message));
+  if (use_binary_protocol_) {
+    auto parsed = protocol::StringUtil::parseMessage(message, false);
+    frontend_->sendRawNotification(parsed->serializeToBinary());
+  } else {
+    frontend_->sendRawNotification(std::move(message));
+  }
 }
 
 void TracingHandler::OnTraceComplete() {
diff --git a/content/browser/devtools/protocol/tracing_handler.h b/content/browser/devtools/protocol/tracing_handler.h
index 97ba228..b07104f8 100644
--- a/content/browser/devtools/protocol/tracing_handler.h
+++ b/content/browser/devtools/protocol/tracing_handler.h
@@ -46,7 +46,8 @@
 class TracingHandler : public DevToolsDomainHandler, public Tracing::Backend {
  public:
   CONTENT_EXPORT TracingHandler(FrameTreeNode* frame_tree_node,
-                                DevToolsIOContext* io_context);
+                                DevToolsIOContext* io_context,
+                                bool use_binary_protocol);
   CONTENT_EXPORT ~TracingHandler() override;
 
   static std::vector<TracingHandler*> ForAgentHost(DevToolsAgentHostImpl* host);
@@ -123,6 +124,7 @@
                        std::unordered_set<base::ProcessId>* process_set);
   void OnProcessReady(RenderProcessHost*);
 
+  const bool use_binary_protocol_;
   std::unique_ptr<base::RepeatingTimer> buffer_usage_poll_timer_;
 
   std::unique_ptr<Tracing::Frontend> frontend_;
diff --git a/content/browser/devtools/protocol/tracing_handler_unittest.cc b/content/browser/devtools/protocol/tracing_handler_unittest.cc
index a3927429..9db9c9c8 100644
--- a/content/browser/devtools/protocol/tracing_handler_unittest.cc
+++ b/content/browser/devtools/protocol/tracing_handler_unittest.cc
@@ -72,7 +72,7 @@
 class TracingHandlerTest : public testing::Test {
  public:
   void SetUp() override {
-    tracing_handler_.reset(new TracingHandler(nullptr, nullptr));
+    tracing_handler_.reset(new TracingHandler(nullptr, nullptr, false));
   }
 
   void TearDown() override { tracing_handler_.reset(); }
diff --git a/content/browser/devtools/render_frame_devtools_agent_host.cc b/content/browser/devtools/render_frame_devtools_agent_host.cc
index cade752..768ae60f 100644
--- a/content/browser/devtools/render_frame_devtools_agent_host.cc
+++ b/content/browser/devtools/render_frame_devtools_agent_host.cc
@@ -305,7 +305,8 @@
   session->AddHandler(base::WrapUnique(new protocol::SecurityHandler()));
   if (!frame_tree_node_ || !frame_tree_node_->parent()) {
     session->AddHandler(base::WrapUnique(
-        new protocol::TracingHandler(frame_tree_node_, GetIOContext())));
+        new protocol::TracingHandler(frame_tree_node_, GetIOContext(),
+                                     session->client()->UsesBinaryProtocol())));
   }
 
   if (sessions().empty()) {
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index 2d622736..24b06a7a 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -24,6 +24,7 @@
 #include "base/numerics/safe_conversions.h"
 #include "base/process/kill.h"
 #include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/task/post_task.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
@@ -6053,6 +6054,33 @@
     VLOG(1) << "Blocked URL " << validated_params->url.spec();
     LogRendererKillCrashKeys(GetSiteInstance()->GetSiteURL());
 
+    // Temporary instrumentation to debug the root cause of
+    // https://crbug.com/931895.
+    auto bool_to_crash_key = [](bool b) { return b ? "true" : "false"; };
+    base::debug::SetCrashKeyString(
+        base::debug::AllocateCrashKeyString("is_same_document",
+                                            base::debug::CrashKeySize::Size32),
+        bool_to_crash_key(is_same_document_navigation));
+
+    base::debug::SetCrashKeyString(
+        base::debug::AllocateCrashKeyString("is_subframe",
+                                            base::debug::CrashKeySize::Size32),
+        bool_to_crash_key(!frame_tree_node_->IsMainFrame()));
+
+    if (navigation_request_ && navigation_request_->navigation_handle()) {
+      NavigationHandleImpl* handle = navigation_request_->navigation_handle();
+      base::debug::SetCrashKeyString(
+          base::debug::AllocateCrashKeyString(
+              "is_error_page", base::debug::CrashKeySize::Size32),
+          bool_to_crash_key(handle->IsErrorPage()));
+      if (handle->IsErrorPage()) {
+        base::debug::SetCrashKeyString(
+            base::debug::AllocateCrashKeyString(
+                "is_error_page", base::debug::CrashKeySize::Size32),
+            base::IntToString(handle->GetNetErrorCode()));
+      }
+    }
+
     // Kills the process.
     bad_message::ReceivedBadMessage(process,
                                     bad_message::RFH_CAN_COMMIT_URL_BLOCKED);
diff --git a/content/browser/payments/payment_app_content_unittest_base.cc b/content/browser/payments/payment_app_content_unittest_base.cc
index 5194da83..5fcacbd 100644
--- a/content/browser/payments/payment_app_content_unittest_base.cc
+++ b/content/browser/payments/payment_app_content_unittest_base.cc
@@ -14,6 +14,8 @@
 #include "base/run_loop.h"
 #include "base/stl_util.h"
 #include "content/browser/service_worker/embedded_worker_test_helper.h"
+#include "content/browser/service_worker/fake_embedded_worker_instance_client.h"
+#include "content/browser/service_worker/fake_service_worker.h"
 #include "content/browser/service_worker/service_worker_context_wrapper.h"
 #include "content/browser/storage_partition_impl.h"
 #include "content/public/test/test_browser_context.h"
@@ -61,68 +63,74 @@
             blink::mojom::kInvalidServiceWorkerRegistrationId) {}
   ~PaymentAppForWorkerTestHelper() override {}
 
-  void OnStartWorker(
-      int embedded_worker_id,
-      int64_t service_worker_version_id,
-      const GURL& scope,
-      const GURL& script_url,
-      bool pause_after_download,
-      blink::mojom::ServiceWorkerRequest service_worker_request,
-      blink::mojom::ControllerServiceWorkerRequest controller_request,
-      blink::mojom::EmbeddedWorkerInstanceHostAssociatedPtrInfo instance_host,
-      blink::mojom::ServiceWorkerProviderInfoForStartWorkerPtr provider_info,
-      blink::mojom::ServiceWorkerInstalledScriptsInfoPtr installed_scripts_info)
-      override {
-    ServiceWorkerVersion* version =
-        context()->GetLiveVersion(service_worker_version_id);
-    last_sw_registration_id_ = version->registration_id();
-    last_sw_scope_ = scope;
-    EmbeddedWorkerTestHelper::OnStartWorker(
-        embedded_worker_id, service_worker_version_id, scope, script_url,
-        pause_after_download, std::move(service_worker_request),
-        std::move(controller_request), std::move(instance_host),
-        std::move(provider_info), std::move(installed_scripts_info));
-  }
+  class EmbeddedWorkerInstanceClient : public FakeEmbeddedWorkerInstanceClient {
+   public:
+    explicit EmbeddedWorkerInstanceClient(
+        PaymentAppForWorkerTestHelper* worker_helper)
+        : FakeEmbeddedWorkerInstanceClient(worker_helper),
+          worker_helper_(worker_helper) {}
+    ~EmbeddedWorkerInstanceClient() override = default;
 
-  void OnPaymentRequestEvent(
-      payments::mojom::PaymentRequestEventDataPtr event_data,
-      payments::mojom::PaymentHandlerResponseCallbackPtr response_callback,
-      blink::mojom::ServiceWorker::DispatchPaymentRequestEventCallback callback)
-      override {
-    if (respond_payment_request_immediately) {
-      EmbeddedWorkerTestHelper::OnPaymentRequestEvent(
-          std::move(event_data), std::move(response_callback),
-          std::move(callback));
-    } else {
-      pending_response_callback_ = std::move(response_callback);
-      std::move(callback).Run(
-          blink::mojom::ServiceWorkerEventStatus::COMPLETED);
+    void StartWorker(
+        blink::mojom::EmbeddedWorkerStartParamsPtr params) override {
+      ServiceWorkerVersion* version = worker_helper_->context()->GetLiveVersion(
+          params->service_worker_version_id);
+      worker_helper_->last_sw_registration_id_ = version->registration_id();
+      worker_helper_->last_sw_scope_ = version->scope();
+
+      FakeEmbeddedWorkerInstanceClient::StartWorker(std::move(params));
     }
+
+   private:
+    PaymentAppForWorkerTestHelper* const worker_helper_;
+
+    DISALLOW_COPY_AND_ASSIGN(EmbeddedWorkerInstanceClient);
+  };
+
+  class ServiceWorker : public FakeServiceWorker {
+   public:
+    explicit ServiceWorker(PaymentAppForWorkerTestHelper* worker_helper)
+        : FakeServiceWorker(worker_helper), worker_helper_(worker_helper) {}
+    ~ServiceWorker() override = default;
+
+    void DispatchPaymentRequestEvent(
+        payments::mojom::PaymentRequestEventDataPtr event_data,
+        payments::mojom::PaymentHandlerResponseCallbackPtr response_callback,
+        DispatchPaymentRequestEventCallback callback) override {
+      if (!worker_helper_)
+        return;
+      if (worker_helper_->respond_payment_request_immediately_) {
+        FakeServiceWorker::DispatchPaymentRequestEvent(
+            std::move(event_data), std::move(response_callback),
+            std::move(callback));
+      } else {
+        worker_helper_->pending_response_callback_ =
+            std::move(response_callback);
+        std::move(callback).Run(
+            blink::mojom::ServiceWorkerEventStatus::COMPLETED);
+      }
+    }
+
+   private:
+    PaymentAppForWorkerTestHelper* const worker_helper_;
+
+    DISALLOW_COPY_AND_ASSIGN(ServiceWorker);
+  };
+
+  std::unique_ptr<FakeEmbeddedWorkerInstanceClient> CreateInstanceClient()
+      override {
+    return std::make_unique<EmbeddedWorkerInstanceClient>(this);
   }
 
-  void OnCanMakePaymentEvent(
-      payments::mojom::CanMakePaymentEventDataPtr event_data,
-      payments::mojom::PaymentHandlerResponseCallbackPtr response_callback,
-      blink::mojom::ServiceWorker::DispatchCanMakePaymentEventCallback callback)
-      override {
-    EmbeddedWorkerTestHelper::OnCanMakePaymentEvent(
-        std::move(event_data), std::move(response_callback),
-        std::move(callback));
-  }
-
-  void OnAbortPaymentEvent(
-      payments::mojom::PaymentHandlerResponseCallbackPtr response_callback,
-      blink::mojom::ServiceWorker::DispatchCanMakePaymentEventCallback callback)
-      override {
-    EmbeddedWorkerTestHelper::OnAbortPaymentEvent(std::move(response_callback),
-                                                  std::move(callback));
+  std::unique_ptr<FakeServiceWorker> CreateServiceWorker() override {
+    return std::make_unique<ServiceWorker>(this);
   }
 
   int64_t last_sw_registration_id_;
   GURL last_sw_scope_;
 
   // Variables to delay payment request response.
-  bool respond_payment_request_immediately = true;
+  bool respond_payment_request_immediately_ = true;
   payments::mojom::PaymentHandlerResponseCallbackPtr pending_response_callback_;
 
  private:
@@ -222,7 +230,7 @@
 }
 
 void PaymentAppContentUnitTestBase::SetNoPaymentRequestResponseImmediately() {
-  worker_helper_->respond_payment_request_immediately = false;
+  worker_helper_->respond_payment_request_immediately_ = false;
 }
 
 void PaymentAppContentUnitTestBase::RespondPendingPaymentRequest() {
diff --git a/content/browser/picture_in_picture/picture_in_picture_service_impl_unittest.cc b/content/browser/picture_in_picture/picture_in_picture_service_impl_unittest.cc
index ef61e91..6fc0fc2a9 100644
--- a/content/browser/picture_in_picture/picture_in_picture_service_impl_unittest.cc
+++ b/content/browser/picture_in_picture/picture_in_picture_service_impl_unittest.cc
@@ -62,6 +62,7 @@
   gfx::Rect GetVideoBounds() override { return gfx::Rect(); }
   void SetSkipAdButtonVisibility(bool is_visible) override {}
   void SetNextTrackButtonVisibility(bool is_visible) override {}
+  void SetPreviousTrackButtonVisibility(bool is_visible) override {}
 
  private:
   DISALLOW_COPY_AND_ASSIGN(TestOverlayWindow);
diff --git a/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.cc b/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.cc
index ae11346a..824a42b3 100644
--- a/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.cc
+++ b/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.cc
@@ -74,19 +74,24 @@
   DCHECK(surface_id_.is_valid());
 
   MediaSessionImpl* media_session = MediaSessionImpl::Get(initiator_);
-  media_session_action_next_track_handled_ = media_session->ShouldRouteAction(
-      media_session::mojom::MediaSessionAction::kNextTrack);
   media_session_action_play_handled_ = media_session->ShouldRouteAction(
       media_session::mojom::MediaSessionAction::kPlay);
   media_session_action_pause_handled_ = media_session->ShouldRouteAction(
       media_session::mojom::MediaSessionAction::kPause);
   media_session_action_skip_ad_handled_ = media_session->ShouldRouteAction(
       media_session::mojom::MediaSessionAction::kSkipAd);
+  media_session_action_next_track_handled_ = media_session->ShouldRouteAction(
+      media_session::mojom::MediaSessionAction::kNextTrack);
+  media_session_action_previous_track_handled_ =
+      media_session->ShouldRouteAction(
+          media_session::mojom::MediaSessionAction::kPreviousTrack);
 
   UpdatePlayPauseButtonVisibility();
   window_->SetSkipAdButtonVisibility(media_session_action_skip_ad_handled_);
   window_->SetNextTrackButtonVisibility(
       media_session_action_next_track_handled_);
+  window_->SetPreviousTrackButtonVisibility(
+      media_session_action_previous_track_handled_);
   window_->ShowInactive();
   initiator_->SetHasPictureInPictureVideo(true);
 
@@ -258,6 +263,11 @@
     MediaSession::Get(initiator_)->NextTrack();
 }
 
+void PictureInPictureWindowControllerImpl::PreviousTrack() {
+  if (media_session_action_previous_track_handled_)
+    MediaSession::Get(initiator_)->PreviousTrack();
+}
+
 void PictureInPictureWindowControllerImpl::MediaSessionActionsChanged(
     const std::set<media_session::mojom::MediaSessionAction>& actions) {
   // TODO(crbug.com/919842): Currently, the first Media Session to be created
@@ -265,9 +275,6 @@
   // Skip Ad button for a PiP video from another frame. Ideally, we should have
   // a Media Session per frame, not per tab. This is not implemented yet.
 
-  media_session_action_next_track_handled_ =
-      actions.find(media_session::mojom::MediaSessionAction::kNextTrack) !=
-      actions.end();
   media_session_action_pause_handled_ =
       actions.find(media_session::mojom::MediaSessionAction::kPause) !=
       actions.end();
@@ -277,6 +284,12 @@
   media_session_action_skip_ad_handled_ =
       actions.find(media_session::mojom::MediaSessionAction::kSkipAd) !=
       actions.end();
+  media_session_action_next_track_handled_ =
+      actions.find(media_session::mojom::MediaSessionAction::kNextTrack) !=
+      actions.end();
+  media_session_action_previous_track_handled_ =
+      actions.find(media_session::mojom::MediaSessionAction::kPreviousTrack) !=
+      actions.end();
 
   if (!window_)
     return;
@@ -285,6 +298,8 @@
   window_->SetSkipAdButtonVisibility(media_session_action_skip_ad_handled_);
   window_->SetNextTrackButtonVisibility(
       media_session_action_next_track_handled_);
+  window_->SetPreviousTrackButtonVisibility(
+      media_session_action_previous_track_handled_);
 }
 
 void PictureInPictureWindowControllerImpl::MediaStartedPlaying(
diff --git a/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.h b/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.h
index e4655cdd..9e2516c 100644
--- a/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.h
+++ b/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.h
@@ -61,6 +61,7 @@
   CONTENT_EXPORT void SetAlwaysHidePlayPauseButton(bool is_visible) override;
   CONTENT_EXPORT void SkipAd() override;
   CONTENT_EXPORT void NextTrack() override;
+  CONTENT_EXPORT void PreviousTrack() override;
 
   CONTENT_EXPORT void MediaSessionActionsChanged(
       const std::set<media_session::mojom::MediaSessionAction>& actions);
@@ -118,10 +119,11 @@
 
   // Used to show/hide some actions in Picture-in-Picture window. These are set
   // to true when website handles some Media Session actions.
-  bool media_session_action_next_track_handled_ = false;
   bool media_session_action_play_handled_ = false;
   bool media_session_action_pause_handled_ = false;
   bool media_session_action_skip_ad_handled_ = false;
+  bool media_session_action_next_track_handled_ = false;
+  bool media_session_action_previous_track_handled_ = false;
 
   // Used to hide play/pause button if video is a MediaStream or has infinite
   // duration. Play/pause button visibility can be overridden by the Media
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index b33f30b..7298233 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -2833,7 +2833,7 @@
   if (!SiteInstanceImpl::IsOriginLockASite(lock_url))
     return;
 
-  GetRendererInterface()->SetIsLockedToSite();
+  GetRendererInterface()->SetIsLockedToSite(lock_url);
 }
 
 bool RenderProcessHostImpl::IsForGuestsOnly() {
diff --git a/content/browser/service_worker/embedded_worker_instance.cc b/content/browser/service_worker/embedded_worker_instance.cc
index 706e2c5d..c3b969f 100644
--- a/content/browser/service_worker/embedded_worker_instance.cc
+++ b/content/browser/service_worker/embedded_worker_instance.cc
@@ -177,7 +177,8 @@
 // ServiceWorkerScriptLoaderFactory and the other is for passing to the
 // renderer. These bundles include factories for non-network URLs like
 // chrome-extension:// as needed.
-void SetupOnUIThread(base::WeakPtr<ServiceWorkerProcessManager> process_manager,
+void SetupOnUIThread(int embedded_worker_id,
+                     base::WeakPtr<ServiceWorkerProcessManager> process_manager,
                      bool can_use_existing_process,
                      blink::mojom::EmbeddedWorkerStartParamsPtr params,
                      blink::mojom::EmbeddedWorkerInstanceClientRequest request,
@@ -207,8 +208,8 @@
   // Get a process.
   blink::ServiceWorkerStatusCode status =
       process_manager->AllocateWorkerProcess(
-          params->embedded_worker_id, params->script_url,
-          can_use_existing_process, process_info.get());
+          embedded_worker_id, params->script_url, can_use_existing_process,
+          process_info.get());
   if (status != blink::ServiceWorkerStatusCode::kOk) {
     base::PostTaskWithTraits(
         FROM_HERE, {BrowserThread::IO},
@@ -534,7 +535,6 @@
     bool can_use_existing_process =
         context->GetVersionFailureCount(params->service_worker_version_id) <
         kMaxSameProcessFailureCount;
-    DCHECK_EQ(params->embedded_worker_id, instance_->embedded_worker_id_);
     TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("ServiceWorker", "ALLOCATING_PROCESS",
                                       this);
     base::WeakPtr<ServiceWorkerProcessManager> process_manager =
@@ -545,8 +545,9 @@
     base::PostTaskWithTraits(
         FROM_HERE, {BrowserThread::UI},
         base::BindOnce(
-            &SetupOnUIThread, process_manager, can_use_existing_process,
-            std::move(params), std::move(request_), context.get(), context,
+            &SetupOnUIThread, instance_->embedded_worker_id(), process_manager,
+            can_use_existing_process, std::move(params), std::move(request_),
+            context.get(), context,
             base::BindOnce(&StartTask::OnSetupCompleted,
                            weak_factory_.GetWeakPtr(), process_manager)));
   }
@@ -686,7 +687,6 @@
   for (auto& observer : listener_list_)
     observer.OnStarting();
 
-  params->embedded_worker_id = embedded_worker_id_;
   params->worker_devtools_agent_route_id = MSG_ROUTING_NONE;
   params->wait_for_debugger = false;
   params->v8_cache_options = GetV8CacheOptions();
diff --git a/content/browser/service_worker/embedded_worker_instance_unittest.cc b/content/browser/service_worker/embedded_worker_instance_unittest.cc
index 7f2492a..8ed9641 100644
--- a/content/browser/service_worker/embedded_worker_instance_unittest.cc
+++ b/content/browser/service_worker/embedded_worker_instance_unittest.cc
@@ -18,6 +18,7 @@
 #include "content/browser/service_worker/embedded_worker_registry.h"
 #include "content/browser/service_worker/embedded_worker_status.h"
 #include "content/browser/service_worker/embedded_worker_test_helper.h"
+#include "content/browser/service_worker/fake_embedded_worker_instance_client.h"
 #include "content/browser/service_worker/service_worker_context_core.h"
 #include "content/browser/service_worker/service_worker_context_wrapper.h"
 #include "content/browser/service_worker/service_worker_registration.h"
@@ -215,12 +216,6 @@
     return context()->embedded_worker_registry();
   }
 
-  std::vector<std::unique_ptr<
-      EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient>>*
-  mock_instance_clients() {
-    return helper_->mock_instance_clients();
-  }
-
   // Mojo endpoints.
   std::vector<blink::mojom::ServiceWorkerPtr> service_workers_;
   std::vector<blink::mojom::ControllerServiceWorkerPtr> controllers_;
@@ -238,60 +233,6 @@
   DISALLOW_COPY_AND_ASSIGN(EmbeddedWorkerInstanceTest);
 };
 
-// A helper to simulate the start worker sequence is stalled in a worker
-// process.
-class StalledInStartWorkerHelper : public EmbeddedWorkerTestHelper {
- public:
-  StalledInStartWorkerHelper() : EmbeddedWorkerTestHelper(base::FilePath()) {}
-  ~StalledInStartWorkerHelper() override {}
-
-  void OnStartWorker(
-      int embedded_worker_id,
-      int64_t service_worker_version_id,
-      const GURL& scope,
-      const GURL& script_url,
-      bool pause_after_download,
-      blink::mojom::ServiceWorkerRequest service_worker_request,
-      blink::mojom::ControllerServiceWorkerRequest controller_request,
-      blink::mojom::EmbeddedWorkerInstanceHostAssociatedPtrInfo instance_host,
-      blink::mojom::ServiceWorkerProviderInfoForStartWorkerPtr provider_info,
-      blink::mojom::ServiceWorkerInstalledScriptsInfoPtr installed_scripts_info)
-      override {
-    if (force_stall_in_start_) {
-      // Prepare for OnStopWorker().
-      instance_host_ptr_map_[embedded_worker_id].Bind(std::move(instance_host));
-      // Do nothing to simulate a stall in the worker process.
-      return;
-    }
-    EmbeddedWorkerTestHelper::OnStartWorker(
-        embedded_worker_id, service_worker_version_id, scope, script_url,
-        pause_after_download, std::move(service_worker_request),
-        std::move(controller_request), std::move(instance_host),
-        std::move(provider_info), std::move(installed_scripts_info));
-  }
-
-  void OnStopWorker(int embedded_worker_id) override {
-    if (instance_host_ptr_map_[embedded_worker_id]) {
-      instance_host_ptr_map_[embedded_worker_id]->OnStopped();
-      base::RunLoop().RunUntilIdle();
-      return;
-    }
-    EmbeddedWorkerTestHelper::OnStopWorker(embedded_worker_id);
-  }
-
-  void set_force_stall_in_start(bool force_stall_in_start) {
-    force_stall_in_start_ = force_stall_in_start;
-  }
-
- private:
-  bool force_stall_in_start_ = true;
-
-  std::map<int /* embedded_worker_id */,
-           blink::mojom::
-               EmbeddedWorkerInstanceHostAssociatedPtr /* instance_host_ptr */>
-      instance_host_ptr_map_;
-};
-
 TEST_P(EmbeddedWorkerInstanceTest, StartAndStop) {
   const GURL scope("http://example.com/");
   const GURL url("http://example.com/worker.js");
@@ -476,12 +417,14 @@
   const GURL scope("http://example.com/");
   const GURL url("http://example.com/worker.js");
 
-  helper_.reset(new StalledInStartWorkerHelper());
   RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
   std::unique_ptr<EmbeddedWorkerInstance> worker =
       embedded_worker_registry()->CreateWorker(pair.second.get());
   worker->AddObserver(this);
 
+  auto* client = helper_->AddNewPendingInstanceClient<
+      DelayedFakeEmbeddedWorkerInstanceClient>(helper_.get());
+  client->UnblockStopWorker();
   StartWorkerUntilStartSent(worker.get(), CreateStartParams(pair.second));
   ASSERT_EQ(2u, events_.size());
   EXPECT_EQ(PROCESS_ALLOCATED, events_[0].type);
@@ -543,12 +486,12 @@
 }
 
 class DontReceiveResumeAfterDownloadInstanceClient
-    : public EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient {
+    : public FakeEmbeddedWorkerInstanceClient {
  public:
-  explicit DontReceiveResumeAfterDownloadInstanceClient(
+  DontReceiveResumeAfterDownloadInstanceClient(
       EmbeddedWorkerTestHelper* helper,
       bool* was_resume_after_download_called)
-      : EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient(helper),
+      : FakeEmbeddedWorkerInstanceClient(helper),
         was_resume_after_download_called_(was_resume_after_download_called) {}
 
  private:
@@ -564,7 +507,7 @@
   const GURL url("http://example.com/worker.js");
 
   bool was_resume_after_download_called = false;
-  helper_->RegisterMockInstanceClient(
+  helper_->AddPendingInstanceClient(
       std::make_unique<DontReceiveResumeAfterDownloadInstanceClient>(
           helper_.get(), &was_resume_after_download_called));
 
@@ -597,12 +540,15 @@
   const GURL scope("http://example.com/");
   const GURL url("http://example.com/worker.js");
 
-  helper_.reset(new StalledInStartWorkerHelper);
   RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
   std::unique_ptr<EmbeddedWorkerInstance> worker =
       embedded_worker_registry()->CreateWorker(pair.second.get());
   worker->AddObserver(this);
 
+  auto* client = helper_->AddNewPendingInstanceClient<
+      DelayedFakeEmbeddedWorkerInstanceClient>(helper_.get());
+  client->UnblockStopWorker();
+
   StartWorkerUntilStartSent(worker.get(), CreateStartParams(pair.second));
   ASSERT_EQ(2u, events_.size());
   EXPECT_EQ(PROCESS_ALLOCATED, events_[0].type);
@@ -622,8 +568,6 @@
   events_.clear();
 
   // Restart the worker.
-  static_cast<StalledInStartWorkerHelper*>(helper_.get())
-      ->set_force_stall_in_start(false);
   StartWorker(worker.get(), CreateStartParams(pair.second));
 
   // The worker should be started.
@@ -667,7 +611,7 @@
   const GURL url("http://example.com/worker.js");
 
   // Let StartWorker fail; mojo IPC fails to connect to a remote interface.
-  helper_->RegisterMockInstanceClient(nullptr);
+  helper_->AddPendingInstanceClient(nullptr);
 
   RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
   std::unique_ptr<EmbeddedWorkerInstance> worker =
@@ -695,28 +639,10 @@
   EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, worker->status());
 }
 
-class FailEmbeddedWorkerInstanceClientImpl
-    : public EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient {
- public:
-  explicit FailEmbeddedWorkerInstanceClientImpl(
-      EmbeddedWorkerTestHelper* helper)
-      : EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient(helper) {}
-
- private:
-  void StartWorker(blink::mojom::EmbeddedWorkerStartParamsPtr) override {
-    helper_->mock_instance_clients()->clear();
-  }
-};
-
 TEST_P(EmbeddedWorkerInstanceTest, RemoveRemoteInterface) {
   const GURL scope("http://example.com/");
   const GURL url("http://example.com/worker.js");
 
-  // Let StartWorker fail; binding is discarded in the middle of IPC
-  helper_->RegisterMockInstanceClient(
-      std::make_unique<FailEmbeddedWorkerInstanceClientImpl>(helper_.get()));
-  ASSERT_EQ(mock_instance_clients()->size(), 1UL);
-
   RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
   std::unique_ptr<EmbeddedWorkerInstance> worker =
       embedded_worker_registry()->CreateWorker(pair.second.get());
@@ -725,12 +651,17 @@
   // Attempt to start the worker.
   base::Optional<blink::ServiceWorkerStatusCode> status;
   base::RunLoop loop;
+  auto* client = helper_->AddNewPendingInstanceClient<
+      DelayedFakeEmbeddedWorkerInstanceClient>(helper_.get());
   worker->Start(CreateStartParams(pair.second),
                 ReceiveStatus(&status, loop.QuitClosure()));
   loop.Run();
   EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status.value());
 
-  // Worker should handle the sudden shutdown as detach.
+  // Disconnect the Mojo connection. Worker should handle the sudden shutdown as
+  // detach.
+  client->RunUntilBound();
+  client->Disconnect();
   base::RunLoop().RunUntilIdle();
   ASSERT_EQ(3u, events_.size());
   EXPECT_EQ(PROCESS_ALLOCATED, events_[0].type);
@@ -739,41 +670,47 @@
   EXPECT_EQ(EmbeddedWorkerStatus::STARTING, events_[2].status.value());
 }
 
-class StoreMessageInstanceClient
-    : public EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient {
+class StoreMessageInstanceClient : public FakeEmbeddedWorkerInstanceClient {
  public:
   explicit StoreMessageInstanceClient(EmbeddedWorkerTestHelper* helper)
-      : EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient(helper) {}
+      : FakeEmbeddedWorkerInstanceClient(helper) {}
 
+  // Returns messages from AddMessageToConsole.
   const std::vector<std::pair<blink::mojom::ConsoleMessageLevel, std::string>>&
-  message() {
-    return messages_;
+  console_messages() {
+    return console_messages_;
+  }
+
+  void SetAddMessageToConsoleReceivedCallback(
+      const base::RepeatingClosure& closure) {
+    add_message_to_console_callback_ = closure;
   }
 
  private:
   void AddMessageToConsole(blink::mojom::ConsoleMessageLevel level,
                            const std::string& message) override {
-    messages_.push_back(std::make_pair(level, message));
+    console_messages_.emplace_back(level, message);
+    if (add_message_to_console_callback_)
+      add_message_to_console_callback_.Run();
   }
 
   std::vector<std::pair<blink::mojom::ConsoleMessageLevel, std::string>>
-      messages_;
+      console_messages_;
+  base::RepeatingClosure add_message_to_console_callback_;
 };
 
 TEST_P(EmbeddedWorkerInstanceTest, AddMessageToConsole) {
   const GURL scope("http://example.com/");
   const GURL url("http://example.com/worker.js");
-  std::unique_ptr<StoreMessageInstanceClient> instance_client =
-      std::make_unique<StoreMessageInstanceClient>(helper_.get());
-  StoreMessageInstanceClient* instance_client_rawptr = instance_client.get();
-  helper_->RegisterMockInstanceClient(std::move(instance_client));
-  ASSERT_EQ(mock_instance_clients()->size(), 1UL);
-
   RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
   std::unique_ptr<EmbeddedWorkerInstance> worker =
       embedded_worker_registry()->CreateWorker(pair.second.get());
   worker->AddObserver(this);
 
+  auto* client =
+      helper_->AddNewPendingInstanceClient<StoreMessageInstanceClient>(
+          helper_.get());
+
   // Attempt to start the worker and immediate AddMessageToConsole should not
   // cause a crash.
   std::pair<blink::mojom::ConsoleMessageLevel, std::string> test_message =
@@ -787,19 +724,21 @@
   EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, worker->status());
 
   // Messages sent before sending StartWorker message won't be dispatched.
-  ASSERT_EQ(0UL, instance_client_rawptr->message().size());
+  ASSERT_EQ(0UL, client->console_messages().size());
   ASSERT_EQ(3UL, events_.size());
   EXPECT_EQ(PROCESS_ALLOCATED, events_[0].type);
   EXPECT_EQ(START_WORKER_MESSAGE_SENT, events_[1].type);
   EXPECT_EQ(STARTED, events_[2].type);
 
+  base::RunLoop loop;
+  client->SetAddMessageToConsoleReceivedCallback(loop.QuitClosure());
   worker->AddMessageToConsole(test_message.first, test_message.second);
-  base::RunLoop().RunUntilIdle();
+  loop.Run();
 
   // Messages sent after sending StartWorker message should be reached to
   // the renderer.
-  ASSERT_EQ(1UL, instance_client_rawptr->message().size());
-  EXPECT_EQ(test_message, instance_client_rawptr->message()[0]);
+  ASSERT_EQ(1UL, client->console_messages().size());
+  EXPECT_EQ(test_message, client->console_messages()[0]);
 
   // Ensure the worker is stopped.
   worker->Stop();
@@ -807,30 +746,17 @@
   EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, worker->status());
 }
 
-// Records whether a CacheStoragePtr was sent as part of StartWorker.
-class RecordCacheStorageHelper : public EmbeddedWorkerTestHelper {
+class RecordCacheStorageInstanceClient
+    : public FakeEmbeddedWorkerInstanceClient {
  public:
-  RecordCacheStorageHelper() : EmbeddedWorkerTestHelper(base::FilePath()) {}
-  ~RecordCacheStorageHelper() override {}
+  explicit RecordCacheStorageInstanceClient(EmbeddedWorkerTestHelper* helper)
+      : FakeEmbeddedWorkerInstanceClient(helper) {}
+  ~RecordCacheStorageInstanceClient() override = default;
 
-  void OnStartWorker(
-      int embedded_worker_id,
-      int64_t service_worker_version_id,
-      const GURL& scope,
-      const GURL& script_url,
-      bool pause_after_download,
-      blink::mojom::ServiceWorkerRequest service_worker_request,
-      blink::mojom::ControllerServiceWorkerRequest controller_request,
-      blink::mojom::EmbeddedWorkerInstanceHostAssociatedPtrInfo instance_host,
-      blink::mojom::ServiceWorkerProviderInfoForStartWorkerPtr provider_info,
-      blink::mojom::ServiceWorkerInstalledScriptsInfoPtr installed_scripts_info)
-      override {
-    had_cache_storage_ = !!provider_info->cache_storage;
-    EmbeddedWorkerTestHelper::OnStartWorker(
-        embedded_worker_id, service_worker_version_id, scope, script_url,
-        pause_after_download, std::move(service_worker_request),
-        std::move(controller_request), std::move(instance_host),
-        std::move(provider_info), std::move(installed_scripts_info));
+  void StartWorker(
+      blink::mojom::EmbeddedWorkerStartParamsPtr start_params) override {
+    had_cache_storage_ = !!start_params->provider_info->cache_storage;
+    FakeEmbeddedWorkerInstanceClient::StartWorker(std::move(start_params));
   }
 
   bool had_cache_storage() const { return had_cache_storage_; }
@@ -844,9 +770,6 @@
 TEST_P(EmbeddedWorkerInstanceTest, CacheStorageOptimization) {
   const GURL scope("http://example.com/");
   const GURL url("http://example.com/worker.js");
-  auto helper = std::make_unique<RecordCacheStorageHelper>();
-  auto* helper_rawptr = helper.get();
-  helper_ = std::move(helper);
 
   RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
   std::unique_ptr<EmbeddedWorkerInstance> worker =
@@ -855,10 +778,13 @@
   // First, test a worker without pause after download.
   {
     // Start the worker.
+    auto* client =
+        helper_->AddNewPendingInstanceClient<RecordCacheStorageInstanceClient>(
+            helper_.get());
     StartWorker(worker.get(), CreateStartParams(pair.second));
 
     // Cache storage should have been sent.
-    EXPECT_TRUE(helper_rawptr->had_cache_storage());
+    EXPECT_TRUE(client->had_cache_storage());
 
     // Stop the worker.
     worker->Stop();
@@ -867,6 +793,10 @@
 
   // Second, test a worker with pause after download.
   {
+    auto* client =
+        helper_->AddNewPendingInstanceClient<RecordCacheStorageInstanceClient>(
+            helper_.get());
+
     // Start the worker until paused.
     blink::mojom::EmbeddedWorkerStartParamsPtr params =
         CreateStartParams(pair.second);
@@ -881,7 +811,7 @@
     EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, worker->status());
 
     // Cache storage should not have been sent.
-    EXPECT_FALSE(helper_rawptr->had_cache_storage());
+    EXPECT_FALSE(client->had_cache_storage());
 
     // Stop the worker.
     worker->Stop();
@@ -898,9 +828,6 @@
 
   const GURL scope("http://example.com/");
   const GURL url("http://example.com/worker.js");
-  auto helper = std::make_unique<RecordCacheStorageHelper>();
-  auto* helper_rawptr = helper.get();
-  helper_ = std::move(helper);
 
   RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
   std::unique_ptr<EmbeddedWorkerInstance> worker =
@@ -908,13 +835,17 @@
 
   // First, test a worker without pause after download.
   {
+    auto* client =
+        helper_->AddNewPendingInstanceClient<RecordCacheStorageInstanceClient>(
+            helper_.get());
+
     // Start the worker.
     blink::mojom::EmbeddedWorkerStartParamsPtr params =
         CreateStartParams(pair.second);
     StartWorker(worker.get(), std::move(params));
 
     // Cache storage should not have been sent.
-    EXPECT_FALSE(helper_rawptr->had_cache_storage());
+    EXPECT_FALSE(client->had_cache_storage());
 
     // Stop the worker.
     worker->Stop();
@@ -923,6 +854,10 @@
 
   // Second, test a worker with pause after download.
   {
+    auto* client =
+        helper_->AddNewPendingInstanceClient<RecordCacheStorageInstanceClient>(
+            helper_.get());
+
     // Start the worker until paused.
     blink::mojom::EmbeddedWorkerStartParamsPtr params =
         CreateStartParams(pair.second);
@@ -937,7 +872,7 @@
     EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, worker->status());
 
     // Cache storage should not have been sent.
-    EXPECT_FALSE(helper_rawptr->had_cache_storage());
+    EXPECT_FALSE(client->had_cache_storage());
 
     // Stop the worker.
     worker->Stop();
@@ -946,17 +881,18 @@
 }
 
 // Starts the worker with kAbruptCompletion status.
-class AbruptCompletionHelper : public EmbeddedWorkerTestHelper {
+class AbruptCompletionInstanceClient : public FakeEmbeddedWorkerInstanceClient {
  public:
-  AbruptCompletionHelper() : EmbeddedWorkerTestHelper(base::FilePath()) {}
-  ~AbruptCompletionHelper() override = default;
+  AbruptCompletionInstanceClient(EmbeddedWorkerTestHelper* helper)
+      : FakeEmbeddedWorkerInstanceClient(helper) {}
+  ~AbruptCompletionInstanceClient() override = default;
 
-  void OnResumeAfterDownload(int embedded_worker_id) override {
-    SimulateScriptEvaluationStart(embedded_worker_id);
-    SimulateWorkerStarted(
-        embedded_worker_id,
-        blink::mojom::ServiceWorkerStartStatus::kAbruptCompletion,
-        GetNextThreadId());
+ protected:
+  void EvaluateScript() override {
+    host()->OnScriptEvaluationStart();
+    host()->OnStarted(blink::mojom::ServiceWorkerStartStatus::kAbruptCompletion,
+                      helper()->GetNextThreadId(),
+                      blink::mojom::EmbeddedWorkerStartTiming::New());
   }
 };
 
@@ -965,14 +901,14 @@
 TEST_P(EmbeddedWorkerInstanceTest, AbruptCompletion) {
   const GURL scope("http://example.com/");
   const GURL url("http://example.com/worker.js");
-  helper_ = std::make_unique<AbruptCompletionHelper>();
-
   RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
   std::unique_ptr<EmbeddedWorkerInstance> worker =
       embedded_worker_registry()->CreateWorker(pair.second.get());
   EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, worker->status());
   worker->AddObserver(this);
 
+  helper_->AddPendingInstanceClient(
+      std::make_unique<AbruptCompletionInstanceClient>(helper_.get()));
   StartWorker(worker.get(), CreateStartParams(pair.second));
 
   ASSERT_EQ(3u, events_.size());
diff --git a/content/browser/service_worker/embedded_worker_test_helper.cc b/content/browser/service_worker/embedded_worker_test_helper.cc
index 6655cfc3..f4e151c 100644
--- a/content/browser/service_worker/embedded_worker_test_helper.cc
+++ b/content/browser/service_worker/embedded_worker_test_helper.cc
@@ -103,237 +103,6 @@
   DISALLOW_COPY_AND_ASSIGN(MockNetworkURLLoaderFactory);
 };
 
-EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient::
-    MockEmbeddedWorkerInstanceClient(EmbeddedWorkerTestHelper* helper)
-    : helper_(helper), binding_(this) {}
-
-EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient::
-    ~MockEmbeddedWorkerInstanceClient() {}
-
-void EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient::StartWorker(
-    blink::mojom::EmbeddedWorkerStartParamsPtr params) {
-  embedded_worker_id_ = params->embedded_worker_id;
-
-  EmbeddedWorkerInstance* worker =
-      helper_->registry()->GetWorker(params->embedded_worker_id);
-  ASSERT_TRUE(worker);
-
-  helper_->OnStartWorkerStub(std::move(params));
-}
-
-void EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient::StopWorker() {
-  ASSERT_TRUE(embedded_worker_id_);
-  EmbeddedWorkerInstance* worker =
-      helper_->registry()->GetWorker(embedded_worker_id_.value());
-  // |worker| is possible to be null when corresponding EmbeddedWorkerInstance
-  // is removed right after sending StopWorker.
-  if (worker)
-    EXPECT_EQ(EmbeddedWorkerStatus::STOPPING, worker->status());
-  helper_->OnStopWorkerStub(embedded_worker_id_.value());
-}
-
-void EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient::
-    ResumeAfterDownload() {
-  helper_->OnResumeAfterDownloadStub(embedded_worker_id_.value());
-}
-
-void EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient::
-    AddMessageToConsole(blink::mojom::ConsoleMessageLevel level,
-                        const std::string& message) {
-  // TODO(shimazu): Pass these arguments to the test helper when a test is
-  // necessary to check them individually.
-}
-
-// static
-void EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient::Bind(
-    EmbeddedWorkerTestHelper* helper,
-    blink::mojom::EmbeddedWorkerInstanceClientRequest request) {
-  std::vector<std::unique_ptr<MockEmbeddedWorkerInstanceClient>>* clients =
-      helper->mock_instance_clients();
-  size_t next_client_index = helper->mock_instance_clients_next_index_;
-
-  ASSERT_GE(clients->size(), next_client_index);
-  if (clients->size() == next_client_index) {
-    clients->push_back(
-        std::make_unique<MockEmbeddedWorkerInstanceClient>(helper));
-  }
-
-  std::unique_ptr<MockEmbeddedWorkerInstanceClient>& client =
-      clients->at(next_client_index);
-  helper->mock_instance_clients_next_index_ = next_client_index + 1;
-  if (client)
-    client->binding_.Bind(std::move(request));
-}
-
-class EmbeddedWorkerTestHelper::MockServiceWorker
-    : public blink::mojom::ServiceWorker {
- public:
-  MockServiceWorker(blink::mojom::ServiceWorkerRequest request,
-                    EmbeddedWorkerTestHelper* helper,
-                    int embedded_worker_id)
-      : helper_(helper),
-        embedded_worker_id_(embedded_worker_id),
-        binding_(this) {
-    binding_.Bind(std::move(request));
-    binding_.set_connection_error_handler(base::BindOnce(
-        &MockServiceWorker::OnConnectionError, base::Unretained(this)));
-  }
-
-  ~MockServiceWorker() override = default;
-
-  void OnConnectionError() {
-    // Destroys |this|.
-    helper_->RemoveServiceWorker(this);
-  }
-
-  void InitializeGlobalScope(
-      blink::mojom::ServiceWorkerHostAssociatedPtrInfo service_worker_host,
-      blink::mojom::ServiceWorkerRegistrationObjectInfoPtr registration_info)
-      override {
-    helper_->OnInitializeGlobalScope(embedded_worker_id_,
-                                     std::move(service_worker_host),
-                                     std::move(registration_info));
-  }
-
-  void DispatchInstallEvent(
-      DispatchInstallEventCallback callback) override {
-    helper_->OnInstallEventStub(std::move(callback));
-  }
-
-  void DispatchActivateEvent(DispatchActivateEventCallback callback) override {
-    helper_->OnActivateEventStub(std::move(callback));
-  }
-
-  void DispatchBackgroundFetchAbortEvent(
-      blink::mojom::BackgroundFetchRegistrationPtr registration,
-      DispatchBackgroundFetchAbortEventCallback callback) override {
-    helper_->OnBackgroundFetchAbortEventStub(std::move(registration),
-                                             std::move(callback));
-  }
-
-  void DispatchBackgroundFetchClickEvent(
-      blink::mojom::BackgroundFetchRegistrationPtr registration,
-      DispatchBackgroundFetchClickEventCallback callback) override {
-    helper_->OnBackgroundFetchClickEventStub(std::move(registration),
-                                             std::move(callback));
-  }
-
-  void DispatchBackgroundFetchFailEvent(
-      blink::mojom::BackgroundFetchRegistrationPtr registration,
-      DispatchBackgroundFetchFailEventCallback callback) override {
-    helper_->OnBackgroundFetchFailEventStub(std::move(registration),
-                                            std::move(callback));
-  }
-
-  void DispatchBackgroundFetchSuccessEvent(
-      blink::mojom::BackgroundFetchRegistrationPtr registration,
-      DispatchBackgroundFetchSuccessEventCallback callback) override {
-    helper_->OnBackgroundFetchSuccessEventStub(std::move(registration),
-                                               std::move(callback));
-  }
-
-  void DispatchCookieChangeEvent(
-      const net::CanonicalCookie& cookie,
-      ::network::mojom::CookieChangeCause cause,
-      DispatchCookieChangeEventCallback callback) override {
-    helper_->OnCookieChangeEventStub(cookie, cause, std::move(callback));
-  }
-
-  void DispatchFetchEvent(
-      blink::mojom::DispatchFetchEventParamsPtr params,
-      blink::mojom::ServiceWorkerFetchResponseCallbackPtr response_callback,
-      DispatchFetchEventCallback callback) override {
-    helper_->OnFetchEventStub(embedded_worker_id_, std::move(params->request),
-                              std::move(params->preload_handle),
-                              std::move(response_callback),
-                              std::move(callback));
-  }
-
-  void DispatchNotificationClickEvent(
-      const std::string& notification_id,
-      const blink::PlatformNotificationData& notification_data,
-      int action_index,
-      const base::Optional<base::string16>& reply,
-      DispatchNotificationClickEventCallback callback) override {
-    helper_->OnNotificationClickEventStub(notification_id, notification_data,
-                                          action_index, reply,
-                                          std::move(callback));
-  }
-
-  void DispatchNotificationCloseEvent(
-      const std::string& notification_id,
-      const blink::PlatformNotificationData& notification_data,
-      DispatchNotificationCloseEventCallback callback) override {
-    helper_->OnNotificationCloseEventStub(notification_id, notification_data,
-                                          std::move(callback));
-  }
-
-  void DispatchPushEvent(const base::Optional<std::string>& payload,
-                         DispatchPushEventCallback callback) override {
-    helper_->OnPushEventStub(payload, std::move(callback));
-  }
-
-  void DispatchSyncEvent(const std::string& tag,
-                         bool last_chance,
-                         base::TimeDelta timeout,
-                         DispatchSyncEventCallback callback) override {
-    NOTIMPLEMENTED();
-  }
-
-  void DispatchAbortPaymentEvent(
-      payments::mojom::PaymentHandlerResponseCallbackPtr response_callback,
-      DispatchAbortPaymentEventCallback callback) override {
-    helper_->OnAbortPaymentEventStub(std::move(response_callback),
-                                     std::move(callback));
-  }
-
-  void DispatchCanMakePaymentEvent(
-      payments::mojom::CanMakePaymentEventDataPtr event_data,
-      payments::mojom::PaymentHandlerResponseCallbackPtr response_callback,
-      DispatchCanMakePaymentEventCallback callback) override {
-    helper_->OnCanMakePaymentEventStub(std::move(event_data),
-                                       std::move(response_callback),
-                                       std::move(callback));
-  }
-
-  void DispatchPaymentRequestEvent(
-      payments::mojom::PaymentRequestEventDataPtr event_data,
-      payments::mojom::PaymentHandlerResponseCallbackPtr response_callback,
-      DispatchPaymentRequestEventCallback callback) override {
-    helper_->OnPaymentRequestEventStub(std::move(event_data),
-                                       std::move(response_callback),
-                                       std::move(callback));
-  }
-
-  void DispatchExtendableMessageEvent(
-      blink::mojom::ExtendableMessageEventPtr event,
-      DispatchExtendableMessageEventCallback callback) override {
-    helper_->OnExtendableMessageEventStub(std::move(event),
-                                          std::move(callback));
-  }
-
-  void DispatchExtendableMessageEventWithCustomTimeout(
-      blink::mojom::ExtendableMessageEventPtr event,
-      base::TimeDelta timeout,
-      DispatchExtendableMessageEventWithCustomTimeoutCallback callback)
-      override {
-    helper_->OnExtendableMessageEventStub(std::move(event),
-                                          std::move(callback));
-  }
-
-  void Ping(PingCallback callback) override { std::move(callback).Run(); }
-
-  void SetIdleTimerDelayToZero() override {
-    helper_->OnSetIdleTimerDelayToZero(embedded_worker_id_);
-  }
-
- private:
-  // |helper_| owns |this|.
-  EmbeddedWorkerTestHelper* helper_;
-  const int embedded_worker_id_;
-  mojo::Binding<blink::mojom::ServiceWorker> binding_;
-};
-
 class EmbeddedWorkerTestHelper::MockRendererInterface : public mojom::Renderer {
  public:
   // |helper| must outlive this.
@@ -354,7 +123,7 @@
   void SetUpEmbeddedWorkerChannelForServiceWorker(
       blink::mojom::EmbeddedWorkerInstanceClientRequest client_request)
       override {
-    MockEmbeddedWorkerInstanceClient::Bind(helper_, std::move(client_request));
+    helper_->OnInstanceClientRequest(std::move(client_request));
   }
   void CreateFrameProxy(
       int32_t routing_id,
@@ -394,7 +163,7 @@
   void SetProcessBackgrounded(bool backgrounded) override { NOTREACHED(); }
   void SetSchedulerKeepActive(bool keep_active) override { NOTREACHED(); }
   void ProcessPurgeAndSuspend() override { NOTREACHED(); }
-  void SetIsLockedToSite() override { NOTREACHED(); }
+  void SetIsLockedToSite(const GURL& lock_url) override { NOTREACHED(); }
   void EnableV8LowMemoryMode() override { NOTREACHED(); }
 
   EmbeddedWorkerTestHelper* helper_;
@@ -410,7 +179,6 @@
           std::make_unique<MockRenderProcessHost>(browser_context_.get())),
       wrapper_(base::MakeRefCounted<ServiceWorkerContextWrapper>(
           browser_context_.get())),
-      mock_instance_clients_next_index_(0),
       next_thread_id_(0),
       mock_render_process_id_(render_process_host_->GetID()),
       new_mock_render_process_id_(new_render_process_host_->GetID()),
@@ -466,16 +234,71 @@
   new_render_process_host_->OverrideURLLoaderFactory(factory);
 }
 
+void EmbeddedWorkerTestHelper::AddPendingInstanceClient(
+    std::unique_ptr<FakeEmbeddedWorkerInstanceClient> client) {
+  pending_embedded_worker_instance_clients_.push(std::move(client));
+}
+
+void EmbeddedWorkerTestHelper::AddPendingServiceWorker(
+    std::unique_ptr<FakeServiceWorker> service_worker) {
+  pending_service_workers_.push(std::move(service_worker));
+}
+
+void EmbeddedWorkerTestHelper::OnInstanceClientRequest(
+    blink::mojom::EmbeddedWorkerInstanceClientRequest request) {
+  std::unique_ptr<FakeEmbeddedWorkerInstanceClient> client;
+  if (!pending_embedded_worker_instance_clients_.empty()) {
+    // Use the instance client that was registered for this message.
+    client = std::move(pending_embedded_worker_instance_clients_.front());
+    pending_embedded_worker_instance_clients_.pop();
+    if (!client) {
+      // Some tests provide a nullptr to drop the request.
+      return;
+    }
+  } else {
+    client = CreateInstanceClient();
+  }
+
+  client->Bind(std::move(request));
+  instance_clients_.insert(std::move(client));
+}
+
+void EmbeddedWorkerTestHelper::OnServiceWorkerRequest(
+    blink::mojom::ServiceWorkerRequest request) {
+  std::unique_ptr<FakeServiceWorker> service_worker;
+  if (!pending_service_workers_.empty()) {
+    // Use the service worker that was registered for this message.
+    service_worker = std::move(pending_service_workers_.front());
+    pending_service_workers_.pop();
+    if (!service_worker) {
+      // Some tests provide a nullptr to drop the request.
+      return;
+    }
+  } else {
+    service_worker = CreateServiceWorker();
+  }
+
+  service_worker->Bind(std::move(request));
+  service_workers_.insert(std::move(service_worker));
+}
+
+void EmbeddedWorkerTestHelper::RemoveInstanceClient(
+    FakeEmbeddedWorkerInstanceClient* instance_client) {
+  auto it = instance_clients_.find(instance_client);
+  instance_clients_.erase(it);
+}
+
+void EmbeddedWorkerTestHelper::RemoveServiceWorker(
+    FakeServiceWorker* service_worker) {
+  auto it = service_workers_.find(service_worker);
+  service_workers_.erase(it);
+}
+
 EmbeddedWorkerTestHelper::~EmbeddedWorkerTestHelper() {
   if (wrapper_.get())
     wrapper_->Shutdown();
 }
 
-void EmbeddedWorkerTestHelper::RegisterMockInstanceClient(
-    std::unique_ptr<MockEmbeddedWorkerInstanceClient> client) {
-  mock_instance_clients_.push_back(std::move(client));
-}
-
 ServiceWorkerContextCore* EmbeddedWorkerTestHelper::context() {
   return wrapper_->context();
 }
@@ -497,83 +320,29 @@
   return info;
 }
 
-void EmbeddedWorkerTestHelper::OnStartWorker(
-    int embedded_worker_id,
-    int64_t service_worker_version_id,
-    const GURL& scope,
-    const GURL& script_url,
-    bool pause_after_download,
-    blink::mojom::ServiceWorkerRequest service_worker_request,
-    blink::mojom::ControllerServiceWorkerRequest controller_request,
-    blink::mojom::EmbeddedWorkerInstanceHostAssociatedPtrInfo instance_host,
-    blink::mojom::ServiceWorkerProviderInfoForStartWorkerPtr provider_info,
-    blink::mojom::ServiceWorkerInstalledScriptsInfoPtr installed_scripts_info) {
-  EmbeddedWorkerInstance* worker = registry()->GetWorker(embedded_worker_id);
-  ASSERT_TRUE(worker);
-  CreateServiceWorker(std::move(service_worker_request), embedded_worker_id);
-  embedded_worker_id_service_worker_version_id_map_[embedded_worker_id] =
-      service_worker_version_id;
-  embedded_worker_id_instance_host_ptr_map_[embedded_worker_id].Bind(
-      std::move(instance_host));
-  embedded_worker_id_installed_scripts_info_map_[embedded_worker_id] =
-      std::move(installed_scripts_info);
-  ServiceWorkerRemoteProviderEndpoint* provider_endpoint =
-      &embedded_worker_id_remote_provider_map_[embedded_worker_id];
-  provider_endpoint->BindWithProviderInfo(std::move(provider_info));
-
-  SimulateWorkerReadyForInspection(embedded_worker_id);
-
-  // In production, new service workers would request their main script here,
-  // which causes the browser to write the script response in service worker
-  // storage. We do that manually here.
-  //
-  // TODO(falken): For new workers, this should use
-  // |script_loader_factory_ptr_info| from |start_params_->provider_info|
-  // to request the script and the browser process should be able to mock it.
-  // For installed workers, the map should already be populated.
-  PopulateScriptCacheMap(
-      service_worker_version_id,
-      base::BindOnce(&EmbeddedWorkerTestHelper::DidPopulateScriptCacheMap,
-                     AsWeakPtr(), embedded_worker_id, pause_after_download));
-}
-
-void EmbeddedWorkerTestHelper::AddServiceWorker(
-    std::unique_ptr<MockServiceWorker> service_worker) {
-  service_workers_.insert(std::move(service_worker));
-}
-
-void EmbeddedWorkerTestHelper::RemoveServiceWorker(
-    MockServiceWorker* service_worker) {
-  auto it = service_workers_.find(service_worker);
-  service_workers_.erase(it);
-}
-
-void EmbeddedWorkerTestHelper::CreateServiceWorker(
-    blink::mojom::ServiceWorkerRequest request,
-    int embedded_worker_id) {
-  AddServiceWorker(std::make_unique<MockServiceWorker>(std::move(request), this,
-                                                       embedded_worker_id));
-}
-
-void EmbeddedWorkerTestHelper::DidPopulateScriptCacheMap(
-    int embedded_worker_id,
-    bool pause_after_download) {
-  SimulateWorkerScriptLoaded(embedded_worker_id);
-  if (!pause_after_download)
-    OnResumeAfterDownload(embedded_worker_id);
-}
-
-void EmbeddedWorkerTestHelper::OnResumeAfterDownload(int embedded_worker_id) {
-  SimulateScriptEvaluationStart(embedded_worker_id);
-  SimulateWorkerStarted(
-      embedded_worker_id,
-      blink::mojom::ServiceWorkerStartStatus::kNormalCompletion,
-      GetNextThreadId());
-}
-
-void EmbeddedWorkerTestHelper::OnStopWorker(int embedded_worker_id) {
-  // By default just notify the sender that the worker is stopped.
-  SimulateWorkerStopped(embedded_worker_id);
+void EmbeddedWorkerTestHelper::PopulateScriptCacheMap(
+    int64_t version_id,
+    base::OnceClosure callback) {
+  ServiceWorkerVersion* version = context()->GetLiveVersion(version_id);
+  if (!version) {
+    std::move(callback).Run();
+    return;
+  }
+  if (!version->script_cache_map()->size()) {
+    std::vector<ServiceWorkerDatabase::ResourceRecord> records;
+    // Add a dummy ResourceRecord for the main script to the script cache map of
+    // the ServiceWorkerVersion.
+    records.push_back(WriteToDiskCacheAsync(
+        context()->storage(), version->script_url(),
+        context()->storage()->NewResourceId(), {} /* headers */, "I'm a body",
+        "I'm a meta data", std::move(callback)));
+    version->script_cache_map()->SetResources(records);
+  }
+  if (!version->GetMainScriptHttpResponseInfo())
+    version->SetMainScriptHttpResponseInfo(CreateHttpResponseInfo());
+  // Call |callback| if |version| already has ResourceRecords.
+  if (!callback.is_null())
+    std::move(callback).Run();
 }
 
 void EmbeddedWorkerTestHelper::OnActivateEvent(
@@ -701,150 +470,14 @@
   // Subclasses may implement this method.
 }
 
-void EmbeddedWorkerTestHelper::PopulateScriptCacheMap(
-    int64_t version_id,
-    base::OnceClosure callback) {
-  ServiceWorkerVersion* version = context()->GetLiveVersion(version_id);
-  if (!version) {
-    std::move(callback).Run();
-    return;
-  }
-  if (!version->script_cache_map()->size()) {
-    std::vector<ServiceWorkerDatabase::ResourceRecord> records;
-    // Add a dummy ResourceRecord for the main script to the script cache map of
-    // the ServiceWorkerVersion.
-    records.push_back(WriteToDiskCacheAsync(
-        context()->storage(), version->script_url(),
-        context()->storage()->NewResourceId(), {} /* headers */, "I'm a body",
-        "I'm a meta data", std::move(callback)));
-    version->script_cache_map()->SetResources(records);
-  }
-  if (!version->GetMainScriptHttpResponseInfo())
-    version->SetMainScriptHttpResponseInfo(CreateHttpResponseInfo());
-  // Call |callback| if |version| already has ResourceRecords.
-  if (!callback.is_null())
-    std::move(callback).Run();
+std::unique_ptr<FakeEmbeddedWorkerInstanceClient>
+EmbeddedWorkerTestHelper::CreateInstanceClient() {
+  return std::make_unique<FakeEmbeddedWorkerInstanceClient>(this);
 }
 
-void EmbeddedWorkerTestHelper::SimulateWorkerReadyForInspection(
-    int embedded_worker_id) {
-  EmbeddedWorkerInstance* worker = registry()->GetWorker(embedded_worker_id);
-  ASSERT_TRUE(worker);
-  ASSERT_TRUE(embedded_worker_id_instance_host_ptr_map_[embedded_worker_id]);
-  embedded_worker_id_instance_host_ptr_map_[embedded_worker_id]
-      ->OnReadyForInspection();
-  base::RunLoop().RunUntilIdle();
-}
-
-void EmbeddedWorkerTestHelper::SimulateWorkerScriptLoaded(
-    int embedded_worker_id) {
-  EmbeddedWorkerInstance* worker = registry()->GetWorker(embedded_worker_id);
-  ASSERT_TRUE(worker);
-  ASSERT_TRUE(embedded_worker_id_instance_host_ptr_map_[embedded_worker_id]);
-  embedded_worker_id_instance_host_ptr_map_[embedded_worker_id]
-      ->OnScriptLoaded();
-  base::RunLoop().RunUntilIdle();
-}
-
-void EmbeddedWorkerTestHelper::SimulateScriptEvaluationStart(
-    int embedded_worker_id) {
-  EmbeddedWorkerInstance* worker = registry()->GetWorker(embedded_worker_id);
-  ASSERT_TRUE(worker);
-  ASSERT_TRUE(embedded_worker_id_instance_host_ptr_map_[embedded_worker_id]);
-  embedded_worker_id_instance_host_ptr_map_[embedded_worker_id]
-      ->OnScriptEvaluationStart();
-  base::RunLoop().RunUntilIdle();
-}
-
-void EmbeddedWorkerTestHelper::SimulateWorkerStarted(
-    int embedded_worker_id,
-    blink::mojom::ServiceWorkerStartStatus status,
-    int thread_id) {
-  EmbeddedWorkerInstance* worker = registry()->GetWorker(embedded_worker_id);
-  ASSERT_TRUE(worker);
-  ASSERT_TRUE(embedded_worker_id_instance_host_ptr_map_[embedded_worker_id]);
-  embedded_worker_id_instance_host_ptr_map_[embedded_worker_id]->OnStarted(
-      status, thread_id, blink::mojom::EmbeddedWorkerStartTiming::New());
-  base::RunLoop().RunUntilIdle();
-}
-
-void EmbeddedWorkerTestHelper::SimulateWorkerStopped(int embedded_worker_id) {
-  EmbeddedWorkerInstance* worker = registry()->GetWorker(embedded_worker_id);
-  if (worker) {
-    ASSERT_TRUE(embedded_worker_id_instance_host_ptr_map_[embedded_worker_id]);
-    embedded_worker_id_instance_host_ptr_map_[embedded_worker_id]->OnStopped();
-    embedded_worker_id_remote_provider_map_.erase(embedded_worker_id);
-    base::RunLoop().RunUntilIdle();
-  }
-}
-
-void EmbeddedWorkerTestHelper::SimulateRequestTermination(
-    int embedded_worker_id,
-    base::OnceCallback<void(bool)> callback) {
-  base::RunLoop loop;
-  ASSERT_TRUE(embedded_worker_id_instance_host_ptr_map_[embedded_worker_id]);
-  embedded_worker_id_instance_host_ptr_map_[embedded_worker_id]
-      ->RequestTermination(std::move(callback));
-  base::RunLoop().RunUntilIdle();
-}
-
-void EmbeddedWorkerTestHelper::OnInitializeGlobalScope(
-    int embedded_worker_id,
-    blink::mojom::ServiceWorkerHostAssociatedPtrInfo service_worker_host,
-    blink::mojom::ServiceWorkerRegistrationObjectInfoPtr registration_info) {
-  embedded_worker_id_host_map_[embedded_worker_id].Bind(
-      std::move(service_worker_host));
-  // To enable the caller end points to make calls safely with no need to pass
-  // these associated interface requests through a message pipe endpoint.
-  mojo::AssociateWithDisconnectedPipe(registration_info->request.PassHandle());
-  if (registration_info->installing) {
-    mojo::AssociateWithDisconnectedPipe(
-        registration_info->installing->request.PassHandle());
-  }
-  if (registration_info->waiting) {
-    mojo::AssociateWithDisconnectedPipe(
-        registration_info->waiting->request.PassHandle());
-  }
-  if (registration_info->active) {
-    mojo::AssociateWithDisconnectedPipe(
-        registration_info->active->request.PassHandle());
-  }
-  // Keep all Mojo connections alive.
-  embedded_worker_id_registration_info_map_[embedded_worker_id] =
-      std::move(registration_info);
-}
-
-void EmbeddedWorkerTestHelper::OnStartWorkerStub(
-    blink::mojom::EmbeddedWorkerStartParamsPtr params) {
-  EmbeddedWorkerInstance* worker =
-      registry()->GetWorker(params->embedded_worker_id);
-  ASSERT_TRUE(worker);
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE,
-      base::BindOnce(
-          &EmbeddedWorkerTestHelper::OnStartWorker, AsWeakPtr(),
-          params->embedded_worker_id, params->service_worker_version_id,
-          params->scope, params->script_url, params->pause_after_download,
-          std::move(params->service_worker_request),
-          std::move(params->controller_request),
-          std::move(params->instance_host), std::move(params->provider_info),
-          std::move(params->installed_scripts_info)));
-}
-
-void EmbeddedWorkerTestHelper::OnResumeAfterDownloadStub(
-    int embedded_worker_id) {
-  EmbeddedWorkerInstance* worker = registry()->GetWorker(embedded_worker_id);
-  ASSERT_TRUE(worker);
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE,
-      base::BindOnce(&EmbeddedWorkerTestHelper::OnResumeAfterDownload,
-                     AsWeakPtr(), embedded_worker_id));
-}
-
-void EmbeddedWorkerTestHelper::OnStopWorkerStub(int embedded_worker_id) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(&EmbeddedWorkerTestHelper::OnStopWorker,
-                                AsWeakPtr(), embedded_worker_id));
+std::unique_ptr<FakeServiceWorker>
+EmbeddedWorkerTestHelper::CreateServiceWorker() {
+  return std::make_unique<FakeServiceWorker>(this);
 }
 
 void EmbeddedWorkerTestHelper::OnActivateEventStub(
diff --git a/content/browser/service_worker/embedded_worker_test_helper.h b/content/browser/service_worker/embedded_worker_test_helper.h
index 3c63ce5a..f4bf3e48 100644
--- a/content/browser/service_worker/embedded_worker_test_helper.h
+++ b/content/browser/service_worker/embedded_worker_test_helper.h
@@ -20,6 +20,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/optional.h"
 #include "base/time/time.h"
+#include "content/browser/service_worker/fake_embedded_worker_instance_client.h"
 #include "content/browser/service_worker/fake_service_worker.h"
 #include "content/browser/service_worker/service_worker_test_utils.h"
 #include "content/browser/url_loader_factory_getter.h"
@@ -34,8 +35,6 @@
 #include "third_party/blink/public/mojom/service_worker/service_worker_installed_scripts_manager.mojom.h"
 #include "url/gurl.h"
 
-class GURL;
-
 namespace blink {
 struct PlatformNotificationData;
 }
@@ -43,8 +42,8 @@
 namespace content {
 
 class EmbeddedWorkerRegistry;
-class EmbeddedWorkerTestHelper;
 class MockRenderProcessHost;
+class FakeServiceWorker;
 class ServiceWorkerContextCore;
 class ServiceWorkerContextWrapper;
 class TestBrowserContext;
@@ -52,77 +51,54 @@
 // In-Process EmbeddedWorker test helper.
 //
 // Usage: create an instance of this class to test browser-side embedded worker
-// code without creating a child process.  This class will create a
+// code without creating a child process. This class will create a
 // ServiceWorkerContextWrapper and ServiceWorkerContextCore for you.
 //
-// By default this class just notifies back WorkerStarted and WorkerStopped
-// for StartWorker and StopWorker requests. The default implementation
-// also returns success for event messages (e.g. InstallEvent, FetchEvent).
+// By default, this class uses FakeEmbeddedWorkerInstanceClient which notifies
+// back success for StartWorker and StopWorker requests. It also uses
+// FakeServiceWorker which returns success for event messages (e.g.
+// InstallEvent, FetchEvent).
 //
-// Alternatively consumers can subclass this helper and override On*()
-// methods to add their own logic/verification code.
+// Alternatively consumers can use subclasses of the Fake* classes
+// to add their own logic/verification code.
 //
-// See embedded_worker_instance_unittest.cc for example usages.
+// Example:
+//
+//  class MyClient : public FakeEmbeddedWorkerInstanceClient {
+//    void StartWorker(...) override {
+//      // Do custom stuff.
+//      LOG(INFO) << "in start worker!";
+//    }
+//  };
+//  class MyServiceWorker : public FakeServiceWorker {
+//    void DispatchFetchEvent(...) override {
+//      // Do custom stuff.
+//      LOG(INFO) << "in fetch event!";
+//    }
+//  };
+//
+//  // Set up the fakes.
+//  helper->AddPendingInstanceClient(std::make_unique<MyClient>());
+//  helper->AddPendingServiceWorker(std::make_unique<MyServiceWorker>());
+//
+//  // Run code that starts a worker.
+//  StartWorker();  // "in start worker!"
+//
+//  // Run code that dispatches a fetch event.
+//  Navigate();  // "in fetch event!"
+//
+// See embedded_worker_instance_unittest.cc for more example usages.
 class EmbeddedWorkerTestHelper {
  public:
   enum class Event { Install, Activate };
 
-  class MockEmbeddedWorkerInstanceClient
-      : public blink::mojom::EmbeddedWorkerInstanceClient {
-   public:
-    // |helper| must outlive this.
-    explicit MockEmbeddedWorkerInstanceClient(EmbeddedWorkerTestHelper* helper);
-    ~MockEmbeddedWorkerInstanceClient() override;
-
-    static void Bind(EmbeddedWorkerTestHelper* helper,
-                     blink::mojom::EmbeddedWorkerInstanceClientRequest request);
-
-   protected:
-    // blink::mojom::EmbeddedWorkerInstanceClient implementation.
-    void StartWorker(
-        blink::mojom::EmbeddedWorkerStartParamsPtr params) override;
-    void StopWorker() override;
-    void ResumeAfterDownload() override;
-    void AddMessageToConsole(blink::mojom::ConsoleMessageLevel level,
-                             const std::string& message) override;
-    void BindDevToolsAgent(
-        blink::mojom::DevToolsAgentHostAssociatedPtrInfo,
-        blink::mojom::DevToolsAgentAssociatedRequest) override {}
-
-    // |helper_| owns |this|.
-    EmbeddedWorkerTestHelper* helper_;
-    mojo::Binding<blink::mojom::EmbeddedWorkerInstanceClient> binding_;
-
-    base::Optional<int> embedded_worker_id_;
-
-   private:
-    DISALLOW_COPY_AND_ASSIGN(MockEmbeddedWorkerInstanceClient);
-  };
-
   // If |user_data_directory| is empty, the context makes storage stuff in
   // memory.
   explicit EmbeddedWorkerTestHelper(const base::FilePath& user_data_directory);
   virtual ~EmbeddedWorkerTestHelper();
 
-  // Simulates Mojo calls to the browser process.
-  void SimulateRequestTermination(int embedded_worker_id,
-                                  base::OnceCallback<void(bool)> callback);
-
-  // Registers a Mojo endpoint object derived from
-  // MockEmbeddedWorkerInstanceClient.
-  void RegisterMockInstanceClient(
-      std::unique_ptr<MockEmbeddedWorkerInstanceClient> client);
-
-  template <typename MockType, typename... Args>
-  MockType* CreateAndRegisterMockInstanceClient(Args&&... args);
-
   std::vector<Event>* dispatched_events() { return &events_; }
 
-  std::vector<std::unique_ptr<MockEmbeddedWorkerInstanceClient>>*
-  mock_instance_clients() {
-    return &mock_instance_clients_;
-  }
-
   ServiceWorkerContextCore* context();
   ServiceWorkerContextWrapper* context_wrapper() { return wrapper_.get(); }
   void ShutdownContext();
@@ -153,34 +129,53 @@
   // null pointer will restore the default behavior.
   void SetNetworkFactory(network::mojom::URLLoaderFactory* factory);
 
+  // Adds the given client to the pending queue. The next time this helper
+  // receives a blink::mojom::EmbeddedWorkerInstanceClientRequest request (i.e.,
+  // on the next start worker attempt), it uses the first client from this
+  // queue.
+  void AddPendingInstanceClient(
+      std::unique_ptr<FakeEmbeddedWorkerInstanceClient> instance_client);
+
+  // Adds the given service worker to the pending queue. The next time this
+  // helper receives a blink::mojom::ServiceWorkerRequest request (i.e., on the
+  // next start worker attempt), it uses the first service worker from this
+  // queue.
+  void AddPendingServiceWorker(
+      std::unique_ptr<FakeServiceWorker> service_worker);
+
+  // A convenience method useful for keeping a pointer to a
+  // FakeEmbeddedWorkerInstanceClient after it's added. Equivalent to:
+  //   auto client_to_pass = std::make_unique<MockType>(args);
+  //   auto* client = client.get();
+  //   AddPendingInstanceClient(std::move(client_to_pass));
+  template <typename MockType, typename... Args>
+  MockType* AddNewPendingInstanceClient(Args&&... args);
+  // Same for FakeServiceWorker.
+  template <typename MockType, typename... Args>
+  MockType* AddNewPendingServiceWorker(Args&&... args);
+
+  /////////////////////////////////////////////////////////////////////////////
+  // The following are exposed to public so the fake embedded worker and service
+  // worker implementations and their subclasses can call them.
+  //
+  // Called when |request| is received. It takes the object from a previous
+  // AddPending*() call if any and calls Create*() otherwise.
+  void OnInstanceClientRequest(
+      blink::mojom::EmbeddedWorkerInstanceClientRequest request);
+  void OnServiceWorkerRequest(blink::mojom::ServiceWorkerRequest request);
+
+  // Called by the fakes to destroy themselves.
+  void RemoveInstanceClient(FakeEmbeddedWorkerInstanceClient* instance_client);
+  void RemoveServiceWorker(FakeServiceWorker* service_worker);
+
   // Writes a dummy script into the given service worker's
   // ServiceWorkerScriptCacheMap. |callback| is called when done.
   virtual void PopulateScriptCacheMap(int64_t service_worker_version_id,
                                       base::OnceClosure callback);
+  /////////////////////////////////////////////////////////////////////////////
 
  protected:
-  // StartWorker IPC handler routed through MockEmbeddedWorkerInstanceClient.
-  // This simulates behaviors in the renderer process. Binds
-  // |service_worker_request| to MockServiceWorker by default.
-  virtual void OnStartWorker(
-      int embedded_worker_id,
-      int64_t service_worker_version_id,
-      const GURL& scope,
-      const GURL& script_url,
-      bool pause_after_download,
-      blink::mojom::ServiceWorkerRequest service_worker_request,
-      blink::mojom::ControllerServiceWorkerRequest controller_request,
-      blink::mojom::EmbeddedWorkerInstanceHostAssociatedPtrInfo instance_host,
-      blink::mojom::ServiceWorkerProviderInfoForStartWorkerPtr provider_info,
-      blink::mojom::ServiceWorkerInstalledScriptsInfoPtr
-          installed_scripts_info);
-  virtual void OnResumeAfterDownload(int embedded_worker_id);
-  // StopWorker IPC handler routed through MockEmbeddedWorkerInstanceClient.
-  // This calls SimulateWorkerStopped() by default.
-  virtual void OnStopWorker(int embedded_worker_id);
-
-  // On*Event handlers. By default they just return success via
-  // SimulateSendReplyToBrowser.
+  // TODO(falken): Remove these and use FakeServiceWorker instead.
   virtual void OnActivateEvent(
       blink::mojom::ServiceWorker::DispatchActivateEventCallback callback);
   virtual void OnBackgroundFetchAbortEvent(
@@ -245,15 +240,6 @@
           callback);
   virtual void OnSetIdleTimerDelayToZero(int embedded_worker_id);
 
-  // These functions simulate making Mojo calls to the browser.
-  void SimulateWorkerReadyForInspection(int embedded_worker_id);
-  void SimulateWorkerScriptLoaded(int embedded_worker_id);
-  void SimulateScriptEvaluationStart(int embedded_worker_id);
-  void SimulateWorkerStarted(int embedded_worker_id,
-                             blink::mojom::ServiceWorkerStartStatus status,
-                             int thread_id);
-  void SimulateWorkerStopped(int embedded_worker_id);
-
   EmbeddedWorkerRegistry* registry();
 
   blink::mojom::ServiceWorkerHost* GetServiceWorkerHost(
@@ -266,28 +252,18 @@
     return embedded_worker_id_instance_host_ptr_map_[embedded_worker_id].get();
   }
 
+  // Subclasses can override these to change the default fakes. This saves tests
+  // from calling AddPending*() for each start worker attempt.
+  virtual std::unique_ptr<FakeEmbeddedWorkerInstanceClient>
+  CreateInstanceClient();
+  virtual std::unique_ptr<FakeServiceWorker> CreateServiceWorker();
+
  private:
   friend FakeServiceWorker;
   class MockNetworkURLLoaderFactory;
-  class MockServiceWorker;
   class MockRendererInterface;
 
-  void AddServiceWorker(std::unique_ptr<MockServiceWorker> service_worker);
-  void RemoveServiceWorker(MockServiceWorker* service_worker);
-  void CreateServiceWorker(blink::mojom::ServiceWorkerRequest request,
-                           int embedded_worker_id);
-
-  void DidPopulateScriptCacheMap(int embedded_worker_id,
-                                 bool pause_after_download);
-
   // TODO(falken): Remove these and use FakeServiceWorker instead.
-  void OnInitializeGlobalScope(
-      int embedded_worker_id,
-      blink::mojom::ServiceWorkerHostAssociatedPtrInfo service_worker_host,
-      blink::mojom::ServiceWorkerRegistrationObjectInfoPtr registration_info);
-  void OnStartWorkerStub(blink::mojom::EmbeddedWorkerStartParamsPtr params);
-  void OnResumeAfterDownloadStub(int embedded_worker_id);
-  void OnStopWorkerStub(int embedded_worker_id);
   void OnActivateEventStub(
       blink::mojom::ServiceWorker::DispatchActivateEventCallback callback);
   void OnBackgroundFetchAbortEventStub(
@@ -358,11 +334,15 @@
   scoped_refptr<ServiceWorkerContextWrapper> wrapper_;
 
   std::unique_ptr<MockRendererInterface> mock_renderer_interface_;
-  std::vector<std::unique_ptr<MockEmbeddedWorkerInstanceClient>>
-      mock_instance_clients_;
-  size_t mock_instance_clients_next_index_;
 
-  base::flat_set<std::unique_ptr<MockServiceWorker>, base::UniquePtrComparator>
+  base::queue<std::unique_ptr<FakeEmbeddedWorkerInstanceClient>>
+      pending_embedded_worker_instance_clients_;
+  base::flat_set<std::unique_ptr<FakeEmbeddedWorkerInstanceClient>,
+                 base::UniquePtrComparator>
+      instance_clients_;
+
+  base::queue<std::unique_ptr<FakeServiceWorker>> pending_service_workers_;
+  base::flat_set<std::unique_ptr<FakeServiceWorker>, base::UniquePtrComparator>
       service_workers_;
 
   int next_thread_id_;
@@ -399,12 +379,21 @@
 };
 
 template <typename MockType, typename... Args>
-MockType* EmbeddedWorkerTestHelper::CreateAndRegisterMockInstanceClient(
+MockType* EmbeddedWorkerTestHelper::AddNewPendingInstanceClient(
     Args&&... args) {
   std::unique_ptr<MockType> mock =
       std::make_unique<MockType>(std::forward<Args>(args)...);
   MockType* mock_rawptr = mock.get();
-  RegisterMockInstanceClient(std::move(mock));
+  AddPendingInstanceClient(std::move(mock));
+  return mock_rawptr;
+}
+
+template <typename MockType, typename... Args>
+MockType* EmbeddedWorkerTestHelper::AddNewPendingServiceWorker(Args&&... args) {
+  std::unique_ptr<MockType> mock =
+      std::make_unique<MockType>(std::forward<Args>(args)...);
+  MockType* mock_rawptr = mock.get();
+  AddPendingServiceWorker(std::move(mock));
   return mock_rawptr;
 }
 
diff --git a/content/browser/service_worker/fake_embedded_worker_instance_client.cc b/content/browser/service_worker/fake_embedded_worker_instance_client.cc
index 612a885..ab886046c 100644
--- a/content/browser/service_worker/fake_embedded_worker_instance_client.cc
+++ b/content/browser/service_worker/fake_embedded_worker_instance_client.cc
@@ -52,9 +52,8 @@
   host_.Bind(std::move(params->instance_host));
   start_params_ = std::move(params);
 
-  // TODO(falken): Uncomment this in the next patch, which implements it.
-  // helper_->OnServiceWorkerRequest(
-  //    std::move(start_params_->service_worker_request));
+  helper_->OnServiceWorkerRequest(
+      std::move(start_params_->service_worker_request));
 
   host_->OnReadyForInspection();
   if (start_params_->is_installed) {
@@ -101,8 +100,7 @@
 
 void FakeEmbeddedWorkerInstanceClient::OnConnectionError() {
   // Destroys |this|.
-  // TODO(falken): Uncomment this in the next patch, which implements it.
-  // helper_->RemoveInstanceClient(this);
+  helper_->RemoveInstanceClient(this);
 }
 
 void FakeEmbeddedWorkerInstanceClient::EvaluateScript() {
diff --git a/content/browser/service_worker/fake_service_worker.cc b/content/browser/service_worker/fake_service_worker.cc
index f13fde58..9a84517 100644
--- a/content/browser/service_worker/fake_service_worker.cc
+++ b/content/browser/service_worker/fake_service_worker.cc
@@ -191,8 +191,7 @@
 
 void FakeServiceWorker::OnConnectionError() {
   // Destroys |this|.
-  // TODO(falken): Uncomment this in the next patch, which implements it.
-  // helper_->RemoveServiceWorker(this);
+  helper_->RemoveServiceWorker(this);
 }
 
 }  // namespace content
diff --git a/content/browser/service_worker/service_worker_context_unittest.cc b/content/browser/service_worker/service_worker_context_unittest.cc
index a31e2fb..7c5b49b 100644
--- a/content/browser/service_worker/service_worker_context_unittest.cc
+++ b/content/browser/service_worker/service_worker_context_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/time/time.h"
 #include "content/browser/service_worker/embedded_worker_registry.h"
 #include "content/browser/service_worker/embedded_worker_test_helper.h"
+#include "content/browser/service_worker/fake_embedded_worker_instance_client.h"
 #include "content/browser/service_worker/service_worker_context_core.h"
 #include "content/browser/service_worker/service_worker_context_core_observer.h"
 #include "content/browser/service_worker/service_worker_context_wrapper.h"
@@ -181,31 +182,32 @@
 };
 
 class RecordableEmbeddedWorkerInstanceClient
-    : public EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient {
+    : public FakeEmbeddedWorkerInstanceClient {
  public:
   enum class Message { StartWorker, StopWorker };
 
   explicit RecordableEmbeddedWorkerInstanceClient(
       EmbeddedWorkerTestHelper* helper)
-      : EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient(helper) {}
+      : FakeEmbeddedWorkerInstanceClient(helper) {}
 
   const std::vector<Message>& events() const { return events_; }
 
  protected:
   void StartWorker(blink::mojom::EmbeddedWorkerStartParamsPtr params) override {
     events_.push_back(Message::StartWorker);
-    EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient::StartWorker(
-        std::move(params));
+    FakeEmbeddedWorkerInstanceClient::StartWorker(std::move(params));
   }
 
   void StopWorker() override {
     events_.push_back(Message::StopWorker);
-    EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient::StopWorker();
+
+    // Let stop complete, but don't call the base class's StopWorker(), which
+    // would destroy |this| before the test can retrieve events().
+    host()->OnStopped();
   }
 
-  std::vector<Message> events_;
-
  private:
+  std::vector<Message> events_;
   DISALLOW_COPY_AND_ASSIGN(RecordableEmbeddedWorkerInstanceClient);
 };
 
@@ -392,9 +394,10 @@
   blink::mojom::ServiceWorkerRegistrationOptions options;
   options.scope = scope;
 
-  RecordableEmbeddedWorkerInstanceClient* client = nullptr;
-  client = helper_->CreateAndRegisterMockInstanceClient<
-      RecordableEmbeddedWorkerInstanceClient>(helper_.get());
+  auto* client =
+      helper_
+          ->AddNewPendingInstanceClient<RecordableEmbeddedWorkerInstanceClient>(
+              helper_.get());
 
   int64_t registration_id = blink::mojom::kInvalidServiceWorkerRegistrationId;
   bool called = false;
@@ -445,9 +448,10 @@
   helper_.reset(new RejectInstallTestHelper);
   helper_->context_wrapper()->AddObserver(this);
 
-  RecordableEmbeddedWorkerInstanceClient* client = nullptr;
-  client = helper_->CreateAndRegisterMockInstanceClient<
-      RecordableEmbeddedWorkerInstanceClient>(helper_.get());
+  auto* client =
+      helper_
+          ->AddNewPendingInstanceClient<RecordableEmbeddedWorkerInstanceClient>(
+              helper_.get());
 
   int64_t registration_id = blink::mojom::kInvalidServiceWorkerRegistrationId;
   bool called = false;
@@ -494,9 +498,10 @@
   helper_.reset(new RejectActivateTestHelper);
   helper_->context_wrapper()->AddObserver(this);
 
-  RecordableEmbeddedWorkerInstanceClient* client = nullptr;
-  client = helper_->CreateAndRegisterMockInstanceClient<
-      RecordableEmbeddedWorkerInstanceClient>(helper_.get());
+  auto* client =
+      helper_
+          ->AddNewPendingInstanceClient<RecordableEmbeddedWorkerInstanceClient>(
+              helper_.get());
 
   int64_t registration_id = blink::mojom::kInvalidServiceWorkerRegistrationId;
   bool called = false;
diff --git a/content/browser/service_worker/service_worker_context_wrapper.cc b/content/browser/service_worker/service_worker_context_wrapper.cc
index e2c5dc9..d76100b 100644
--- a/content/browser/service_worker/service_worker_context_wrapper.cc
+++ b/content/browser/service_worker/service_worker_context_wrapper.cc
@@ -357,6 +357,8 @@
     int64_t service_worker_version_id,
     const std::string& request_uuid) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  if (!context())
+    return false;
   ServiceWorkerVersion* version =
       context()->GetLiveVersion(service_worker_version_id);
   if (!version)
@@ -368,6 +370,8 @@
     int64_t service_worker_version_id,
     const std::string& request_uuid) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  if (!context())
+    return false;
   ServiceWorkerVersion* version =
       context()->GetLiveVersion(service_worker_version_id);
   if (!version)
@@ -1024,8 +1028,10 @@
     blink::mojom::ServiceWorkerProviderInfoForWorkerPtr* out_provider_info) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 
+  if (!context_core_)
+    return nullptr;
   return ServiceWorkerProviderHost::PreCreateForSharedWorker(
-      context()->AsWeakPtr(), process_id, out_provider_info);
+      context_core_->AsWeakPtr(), process_id, out_provider_info);
 }
 
 ServiceWorkerContextWrapper::~ServiceWorkerContextWrapper() {
diff --git a/content/browser/service_worker/service_worker_context_wrapper.h b/content/browser/service_worker/service_worker_context_wrapper.h
index ef08660..b3c8807f 100644
--- a/content/browser/service_worker/service_worker_context_wrapper.h
+++ b/content/browser/service_worker/service_worker_context_wrapper.h
@@ -290,6 +290,8 @@
   // connect to the host. The host stays alive as long as this info stays alive
   // (namely, as long as |out_provider_info->host_ptr_info| stays alive).
   //
+  // Returns null if context() is null.
+  //
   // Must be called on the IO thread.
   base::WeakPtr<ServiceWorkerProviderHost> PreCreateHostForSharedWorker(
       int process_id,
diff --git a/content/browser/service_worker/service_worker_job_unittest.cc b/content/browser/service_worker/service_worker_job_unittest.cc
index 7a14515..929b637 100644
--- a/content/browser/service_worker/service_worker_job_unittest.cc
+++ b/content/browser/service_worker/service_worker_job_unittest.cc
@@ -17,6 +17,7 @@
 #include "content/browser/service_worker/embedded_worker_registry.h"
 #include "content/browser/service_worker/embedded_worker_status.h"
 #include "content/browser/service_worker/embedded_worker_test_helper.h"
+#include "content/browser/service_worker/fake_embedded_worker_instance_client.h"
 #include "content/browser/service_worker/service_worker_context_core.h"
 #include "content/browser/service_worker/service_worker_context_wrapper.h"
 #include "content/browser/service_worker/service_worker_disk_cache.h"
@@ -112,24 +113,6 @@
   return base::BindOnce(&SaveUnregistrationCallback, expected_status, called);
 }
 
-bool RequestTermination(EmbeddedWorkerTestHelper* helper,
-                        scoped_refptr<ServiceWorkerVersion> version) {
-  base::RunLoop loop;
-  base::Optional<bool> will_be_terminated;
-  helper->SimulateRequestTermination(
-      version->embedded_worker()->embedded_worker_id(),
-      base::BindOnce(
-          [](base::OnceClosure done,
-             base::Optional<bool>* out_will_be_terminated,
-             bool will_be_terminated) {
-            *out_will_be_terminated = will_be_terminated;
-            std::move(done).Run();
-          },
-          loop.QuitClosure(), &will_be_terminated));
-  loop.Run();
-  return will_be_terminated.value();
-}
-
 }  // namespace
 
 class ServiceWorkerJobTest : public testing::Test {
@@ -493,31 +476,21 @@
   EXPECT_EQ(new_registration_by_scope, old_registration);
 }
 
-class FailToStartWorkerTestHelper : public EmbeddedWorkerTestHelper {
+// An instance client that breaks the Mojo connection upon receiving the
+// Start() message.
+class FailStartInstanceClient : public FakeEmbeddedWorkerInstanceClient {
  public:
-  FailToStartWorkerTestHelper() : EmbeddedWorkerTestHelper(base::FilePath()) {}
+  FailStartInstanceClient(EmbeddedWorkerTestHelper* helper)
+      : FakeEmbeddedWorkerInstanceClient(helper) {}
 
-  void OnStartWorker(
-      int embedded_worker_id,
-      int64_t service_worker_version_id,
-      const GURL& scope,
-      const GURL& script_url,
-      bool pause_after_download,
-      blink::mojom::ServiceWorkerRequest service_worker_request,
-      blink::mojom::ControllerServiceWorkerRequest controller_request,
-      blink::mojom::EmbeddedWorkerInstanceHostAssociatedPtrInfo instance_host,
-      blink::mojom::ServiceWorkerProviderInfoForStartWorkerPtr provider_info,
-      blink::mojom::ServiceWorkerInstalledScriptsInfoPtr installed_scripts_info)
-      override {
-    blink::mojom::EmbeddedWorkerInstanceHostAssociatedPtr instance_host_ptr;
-    instance_host_ptr.Bind(std::move(instance_host));
-    instance_host_ptr->OnStopped();
-    base::RunLoop().RunUntilIdle();
+  void StartWorker(blink::mojom::EmbeddedWorkerStartParamsPtr params) override {
+    // Don't save the Mojo ptrs. The connection breaks.
   }
 };
 
 TEST_F(ServiceWorkerJobTest, Register_FailToStartWorker) {
-  helper_.reset(new FailToStartWorkerTestHelper);
+  helper_->AddPendingInstanceClient(
+      std::make_unique<FailStartInstanceClient>(helper_.get()));
 
   blink::mojom::ServiceWorkerRegistrationOptions options;
   options.scope = GURL("https://www.example.com/");
@@ -976,21 +949,49 @@
       observed_registration_->RemoveListener(this);
   }
 
+  class UpdateJobEmbeddedWorkerInstanceClient
+      : public FakeEmbeddedWorkerInstanceClient {
+   public:
+    UpdateJobEmbeddedWorkerInstanceClient(UpdateJobTestHelper* helper)
+        : FakeEmbeddedWorkerInstanceClient(helper) {}
+    ~UpdateJobEmbeddedWorkerInstanceClient() override = default;
+
+    void set_force_start_worker_failure(bool force_start_worker_failure) {
+      force_start_worker_failure_ = force_start_worker_failure;
+    }
+
+    void ResumeAfterDownload() override {
+      if (force_start_worker_failure_) {
+        host()->OnScriptEvaluationStart();
+        host()->OnStarted(
+            blink::mojom::ServiceWorkerStartStatus::kAbruptCompletion,
+            helper()->GetNextThreadId(),
+            blink::mojom::EmbeddedWorkerStartTiming::New());
+        return;
+      }
+      FakeEmbeddedWorkerInstanceClient::ResumeAfterDownload();
+    }
+
+   private:
+    bool force_start_worker_failure_ = false;
+  };
+
   ServiceWorkerStorage* storage() { return context()->storage(); }
   ServiceWorkerJobCoordinator* job_coordinator() {
     return context()->job_coordinator();
   }
 
-  void set_force_start_worker_failure(bool force_start_worker_failure) {
-    force_start_worker_failure_ = force_start_worker_failure;
-  }
-
   scoped_refptr<ServiceWorkerRegistration> SetupInitialRegistration(
       const GURL& test_origin) {
     blink::mojom::ServiceWorkerRegistrationOptions options;
     options.scope = test_origin.Resolve(kScope);
     scoped_refptr<ServiceWorkerRegistration> registration;
     bool called = false;
+
+    auto client = std::make_unique<UpdateJobEmbeddedWorkerInstanceClient>(this);
+    initial_embedded_worker_instance_client_ = client.get();
+    AddPendingInstanceClient(std::move(client));
+
     job_coordinator()->Register(
         test_origin.Resolve(kScript), options,
         SaveRegistration(blink::ServiceWorkerStatusCode::kOk, &called,
@@ -1049,19 +1050,7 @@
     std::move(callback).Run();
   }
 
-  void OnResumeAfterDownload(int embedded_worker_id) override {
-    if (force_start_worker_failure_) {
-      SimulateScriptEvaluationStart(embedded_worker_id);
-      SimulateWorkerStarted(
-          embedded_worker_id,
-          blink::mojom::ServiceWorkerStartStatus::kAbruptCompletion,
-          GetNextThreadId());
-      return;
-    }
-    EmbeddedWorkerTestHelper::OnResumeAfterDownload(embedded_worker_id);
-  }
-
-  // ServiceWorkerContextCore::Observer overrides
+  // ServiceWorkerContextCoreObserver overrides
   void OnVersionStateChanged(int64_t version_id,
                              const GURL& scope,
                              ServiceWorkerVersion::Status status) override {
@@ -1084,66 +1073,32 @@
   }
 
   void OnRegistrationFailed(ServiceWorkerRegistration* registration) override {
-    NOTREACHED();
+    registration_failed_ = true;
   }
 
   void OnUpdateFound(ServiceWorkerRegistration* registration) override {
     update_found_ = true;
   }
 
+  UpdateJobEmbeddedWorkerInstanceClient*
+      initial_embedded_worker_instance_client_ = nullptr;
   scoped_refptr<ServiceWorkerRegistration> observed_registration_;
   std::vector<AttributeChangeLogEntry> attribute_change_log_;
   std::vector<StateChangeLogEntry> state_change_log_;
   bool update_found_ = false;
+  bool registration_failed_ = false;
   bool force_start_worker_failure_ = false;
   base::Optional<bool> will_be_terminated_;
 
   base::WeakPtrFactory<UpdateJobTestHelper> weak_factory_;
 };
 
-// Helper class for update tests that evicts the active version when the update
-// worker is about to be started.
-class EvictIncumbentVersionHelper : public UpdateJobTestHelper {
- public:
-  EvictIncumbentVersionHelper() {}
-  ~EvictIncumbentVersionHelper() override {}
-
-  void OnStartWorker(
-      int embedded_worker_id,
-      int64_t version_id,
-      const GURL& scope,
-      const GURL& script,
-      bool pause_after_download,
-      blink::mojom::ServiceWorkerRequest service_worker_request,
-      blink::mojom::ControllerServiceWorkerRequest controller_request,
-      blink::mojom::EmbeddedWorkerInstanceHostAssociatedPtrInfo instance_host,
-      blink::mojom::ServiceWorkerProviderInfoForStartWorkerPtr provider_info,
-      blink::mojom::ServiceWorkerInstalledScriptsInfoPtr installed_scripts_info)
-      override {
-    ServiceWorkerVersion* version = context()->GetLiveVersion(version_id);
-    ServiceWorkerRegistration* registration =
-        context()->GetLiveRegistration(version->registration_id());
-    bool is_update = registration->active_version() &&
-                     version != registration->active_version();
-    if (is_update) {
-      // Evict the incumbent worker.
-      ASSERT_FALSE(registration->waiting_version());
-      registration->DeleteVersion(
-          base::WrapRefCounted(registration->active_version()));
-    }
-    UpdateJobTestHelper::OnStartWorker(
-        embedded_worker_id, version_id, scope, script, pause_after_download,
-        std::move(service_worker_request), std::move(controller_request),
-        std::move(instance_host), std::move(provider_info),
-        std::move(installed_scripts_info));
-  }
-
-  void OnRegistrationFailed(ServiceWorkerRegistration* registration) override {
-    registration_failed_ = true;
-  }
-
-  bool registration_failed_ = false;
-};
+void RequestTermination(
+    blink::mojom::EmbeddedWorkerInstanceHostAssociatedPtr* host) {
+  // We can't wait for the callback since StopWorker() arrives before it which
+  // severs the Mojo connection.
+  (*host)->RequestTermination(base::DoNothing());
+}
 
 }  // namespace
 
@@ -1227,7 +1182,11 @@
 
   // Run an update to a worker that loads successfully but fails to start up
   // (script evaluation failure). The check time should be updated.
-  update_helper->set_force_start_worker_failure(true);
+  auto* embedded_worker_instance_client =
+      update_helper->AddNewPendingInstanceClient<
+          UpdateJobTestHelper::UpdateJobEmbeddedWorkerInstanceClient>(
+          update_helper);
+  embedded_worker_instance_client->set_force_start_worker_failure(true);
   registration->set_last_update_check(kYesterday);
   registration->active_version()->StartUpdate();
   base::RunLoop().RunUntilIdle();
@@ -1259,7 +1218,9 @@
     scoped_refptr<ServiceWorkerVersion> new_version =
         registration->waiting_version();
     EXPECT_EQ(2u, update_helper->attribute_change_log_.size());
-    EXPECT_TRUE(RequestTermination(helper_.get(), first_version));
+    UpdateJobTestHelper::UpdateJobEmbeddedWorkerInstanceClient* client =
+        update_helper->initial_embedded_worker_instance_client_;
+    RequestTermination(&client->host());
 
     TestServiceWorkerObserver observer(helper_->context_wrapper());
     observer.RunUntilActivated(new_version.get(), runner);
@@ -1359,6 +1320,9 @@
   // Create a registration with an active version.
   blink::mojom::ServiceWorkerRegistrationOptions options;
   options.scope = GURL("https://www.example.com/one/");
+  auto* initial_client =
+      helper_->AddNewPendingInstanceClient<FakeEmbeddedWorkerInstanceClient>(
+          helper_.get());
   scoped_refptr<ServiceWorkerRegistration> registration = RunRegisterJob(
       GURL("https://www.example.com/service_worker.js"), options);
   auto runner = base::MakeRefCounted<base::TestSimpleTaskRunner>();
@@ -1388,8 +1352,7 @@
     EXPECT_TRUE(waiting_version);
     EXPECT_NE(version.get(), waiting_version);
 
-    EXPECT_TRUE(
-        RequestTermination(helper_.get(), registration->active_version()));
+    RequestTermination(&initial_client->host());
     TestServiceWorkerObserver observer(helper_->context_wrapper());
     observer.RunUntilActivated(waiting_version, runner);
   }
@@ -1404,18 +1367,27 @@
 // Test that update succeeds if the incumbent worker was evicted
 // during the update job (this can happen on disk cache failure).
 TEST_F(ServiceWorkerJobTest, Update_EvictedIncumbent) {
-  EvictIncumbentVersionHelper* update_helper = new EvictIncumbentVersionHelper;
+  UpdateJobTestHelper* update_helper = new UpdateJobTestHelper;
   helper_.reset(update_helper);
   scoped_refptr<ServiceWorkerRegistration> registration =
       update_helper->SetupInitialRegistration(kNewVersionOrigin);
   ASSERT_TRUE(registration.get());
   update_helper->state_change_log_.clear();
 
-  // Run the update job.
   registration->AddListener(update_helper);
   scoped_refptr<ServiceWorkerVersion> first_version =
       registration->active_version();
+  auto* instance_client = helper_->AddNewPendingInstanceClient<
+      DelayedFakeEmbeddedWorkerInstanceClient>(helper_.get());
+
+  // Start the update job and make it block on the worker starting.
+  // Evict the incumbent during that time.
   first_version->StartUpdate();
+  instance_client->RunUntilStartWorker();
+  registration->DeleteVersion(first_version);
+
+  // Finish the update job.
+  instance_client->UnblockStartWorker();
   base::RunLoop().RunUntilIdle();
 
   // Verify results.
@@ -1467,6 +1439,9 @@
   blink::mojom::ServiceWorkerRegistrationOptions options;
   options.scope = GURL("https://www.example.com/one/");
 
+  auto* initial_client =
+      helper_->AddNewPendingInstanceClient<FakeEmbeddedWorkerInstanceClient>(
+          helper_.get());
   scoped_refptr<ServiceWorkerRegistration> registration =
       RunRegisterJob(script1, options);
   auto runner = base::MakeRefCounted<base::TestSimpleTaskRunner>();
@@ -1496,7 +1471,7 @@
   // Make the old version eligible for eviction.
   old_version->RemoveControllee(host->client_uuid());
   if (blink::ServiceWorkerUtils::IsServicificationEnabled())
-    EXPECT_TRUE(RequestTermination(helper_.get(), old_version));
+    RequestTermination(&initial_client->host());
 
   // Wait for activated.
   TestServiceWorkerObserver observer(helper_->context_wrapper());
@@ -1568,6 +1543,9 @@
   blink::mojom::ServiceWorkerRegistrationOptions options;
   options.scope = GURL("https://www.example.com/one/");
 
+  auto* initial_client =
+      helper_->AddNewPendingInstanceClient<FakeEmbeddedWorkerInstanceClient>(
+          helper_.get());
   scoped_refptr<ServiceWorkerRegistration> registration =
       RunRegisterJob(script1, options);
   auto runner = base::MakeRefCounted<base::TestSimpleTaskRunner>();
@@ -1597,7 +1575,7 @@
 
   old_version->RemoveControllee(host->client_uuid());
   if (blink::ServiceWorkerUtils::IsServicificationEnabled())
-    EXPECT_TRUE(RequestTermination(helper_.get(), old_version));
+    RequestTermination(&initial_client->host());
 
   // Wait for activated.
   TestServiceWorkerObserver observer(helper_->context_wrapper());
@@ -1619,6 +1597,9 @@
   blink::mojom::ServiceWorkerRegistrationOptions options;
   options.scope = GURL("https://www.example.com/one/");
 
+  auto* initial_client =
+      helper_->AddNewPendingInstanceClient<FakeEmbeddedWorkerInstanceClient>(
+          helper_.get());
   scoped_refptr<ServiceWorkerRegistration> registration =
       RunRegisterJob(script1, options);
   auto runner = base::MakeRefCounted<base::TestSimpleTaskRunner>();
@@ -1652,7 +1633,7 @@
 
   first_version->RemoveControllee(host->client_uuid());
   if (blink::ServiceWorkerUtils::IsServicificationEnabled())
-    EXPECT_TRUE(RequestTermination(helper_.get(), first_version));
+    RequestTermination(&initial_client->host());
 
   // Wait for activated.
   TestServiceWorkerObserver observer(helper_->context_wrapper());
@@ -1868,11 +1849,12 @@
 }
 
 class CheckPauseAfterDownloadEmbeddedWorkerInstanceClient
-    : public EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient {
+    : public FakeEmbeddedWorkerInstanceClient {
  public:
   explicit CheckPauseAfterDownloadEmbeddedWorkerInstanceClient(
       EmbeddedWorkerTestHelper* helper)
-      : EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient(helper) {}
+      : FakeEmbeddedWorkerInstanceClient(helper) {}
+
   int num_of_startworker() const { return num_of_startworker_; }
   void set_next_pause_after_download(bool expectation) {
     next_pause_after_download_ = expectation;
@@ -1883,8 +1865,7 @@
     ASSERT_TRUE(next_pause_after_download_.has_value());
     EXPECT_EQ(next_pause_after_download_.value(), params->pause_after_download);
     num_of_startworker_++;
-    EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient::StartWorker(
-        std::move(params));
+    FakeEmbeddedWorkerInstanceClient::StartWorker(std::move(params));
   }
 
  private:
@@ -1899,10 +1880,10 @@
 
   std::vector<CheckPauseAfterDownloadEmbeddedWorkerInstanceClient*> clients;
   clients.push_back(
-      helper_->CreateAndRegisterMockInstanceClient<
+      helper_->AddNewPendingInstanceClient<
           CheckPauseAfterDownloadEmbeddedWorkerInstanceClient>(helper_.get()));
   clients.push_back(
-      helper_->CreateAndRegisterMockInstanceClient<
+      helper_->AddNewPendingInstanceClient<
           CheckPauseAfterDownloadEmbeddedWorkerInstanceClient>(helper_.get()));
 
   // The initial version should not pause after download.
@@ -1928,6 +1909,9 @@
   blink::mojom::ServiceWorkerRegistrationOptions options;
   options.scope = GURL("https://www.example.com/");
 
+  auto* initial_client =
+      helper_->AddNewPendingInstanceClient<FakeEmbeddedWorkerInstanceClient>(
+          helper_.get());
   scoped_refptr<ServiceWorkerRegistration> registration =
       RunRegisterJob(script, options);
   auto runner = base::MakeRefCounted<base::TestSimpleTaskRunner>();
@@ -1964,7 +1948,7 @@
     // S13nServiceWorker: Activating the new version won't happen until
     // RequestTermination() is called.
     EXPECT_EQ(first_version.get(), registration->active_version());
-    EXPECT_TRUE(RequestTermination(update_helper, first_version));
+    RequestTermination(&initial_client->host());
   }
 
   TestServiceWorkerObserver observer(helper_->context_wrapper());
@@ -1975,7 +1959,11 @@
 
   // Shutdown.
   update_helper->context()->wrapper()->Shutdown();
-  update_helper->set_force_start_worker_failure(true);
+  auto* embedded_worker_instance_client =
+      update_helper->AddNewPendingInstanceClient<
+          UpdateJobTestHelper::UpdateJobEmbeddedWorkerInstanceClient>(
+          update_helper);
+  embedded_worker_instance_client->set_force_start_worker_failure(true);
 
   // Allow the activation to continue. It will fail, and the worker
   // should not be promoted to ACTIVATED because failure occur
diff --git a/content/browser/service_worker/service_worker_navigation_loader_unittest.cc b/content/browser/service_worker/service_worker_navigation_loader_unittest.cc
index d6709e1..425b53e 100644
--- a/content/browser/service_worker/service_worker_navigation_loader_unittest.cc
+++ b/content/browser/service_worker/service_worker_navigation_loader_unittest.cc
@@ -14,6 +14,8 @@
 #include "base/test/scoped_feature_list.h"
 #include "content/browser/loader/navigation_loader_interceptor.h"
 #include "content/browser/service_worker/embedded_worker_test_helper.h"
+#include "content/browser/service_worker/fake_embedded_worker_instance_client.h"
+#include "content/browser/service_worker/fake_service_worker.h"
 #include "content/browser/service_worker/service_worker_context_core.h"
 #include "content/browser/service_worker/service_worker_registration.h"
 #include "content/browser/service_worker/service_worker_test_utils.h"
@@ -178,20 +180,24 @@
   DISALLOW_COPY_AND_ASSIGN(NavigationPreloadLoaderClient);
 };
 
-// Helper simulates a service worker handling fetch events. The response can be
+// Simulates a service worker handling fetch events. The response can be
 // customized via RespondWith* functions.
-class Helper : public EmbeddedWorkerTestHelper {
+class FetchEventServiceWorker : public FakeServiceWorker {
  public:
-  Helper() : EmbeddedWorkerTestHelper(base::FilePath()) {}
-  ~Helper() override = default;
+  FetchEventServiceWorker(
+      EmbeddedWorkerTestHelper* helper,
+      FakeEmbeddedWorkerInstanceClient* embedded_worker_instance_client)
+      : FakeServiceWorker(helper),
+        embedded_worker_instance_client_(embedded_worker_instance_client) {}
+  ~FetchEventServiceWorker() override = default;
 
-  // Tells this helper to respond to fetch events with the specified blob.
+  // Tells this worker to respond to fetch events with the specified blob.
   void RespondWithBlob(blink::mojom::SerializedBlobPtr blob) {
     response_mode_ = ResponseMode::kBlob;
     blob_body_ = std::move(blob);
   }
 
-  // Tells this helper to respond to fetch events with the specified stream.
+  // Tells this worker to respond to fetch events with the specified stream.
   void RespondWithStream(
       blink::mojom::ServiceWorkerStreamCallbackRequest callback_request,
       mojo::ScopedDataPipeConsumerHandle consumer_handle) {
@@ -201,35 +207,35 @@
     stream_handle_->stream = std::move(consumer_handle);
   }
 
-  // Tells this helper to respond to fetch events with network fallback.
+  // Tells this worker to respond to fetch events with network fallback.
   // i.e., simulate the service worker not calling respondWith().
   void RespondWithFallback() {
     response_mode_ = ResponseMode::kFallbackResponse;
   }
 
-  // Tells this helper to respond to fetch events with an error response.
+  // Tells this worker to respond to fetch events with an error response.
   void RespondWithError() { response_mode_ = ResponseMode::kErrorResponse; }
 
-  // Tells this helper to respond to fetch events with
+  // Tells this worker to respond to fetch events with
   // FetchEvent#preloadResponse. See NavigationPreloadLoaderClient's
   // documentation for details.
   void RespondWithNavigationPreloadResponse() {
     response_mode_ = ResponseMode::kNavigationPreloadResponse;
   }
 
-  // Tells this helper to respond to fetch events with the redirect response.
+  // Tells this worker to respond to fetch events with the redirect response.
   void RespondWithRedirectResponse(const GURL& new_url) {
     response_mode_ = ResponseMode::kRedirect;
     redirected_url_ = new_url;
   }
 
-  // Tells this helper to simulate failure to dispatch the fetch event to the
+  // Tells this worker to simulate failure to dispatch the fetch event to the
   // service worker.
   void FailToDispatchFetchEvent() {
     response_mode_ = ResponseMode::kFailFetchEventDispatch;
   }
 
-  // Tells this helper to simulate "early response", where the respondWith()
+  // Tells this worker to simulate "early response", where the respondWith()
   // promise resolves before the waitUntil() promise. In this mode, the
   // helper sets the response mode to "early response", which simulates the
   // promise passed to respondWith() resolving before the waitUntil() promise
@@ -243,7 +249,7 @@
     base::RunLoop().RunUntilIdle();
   }
 
-  // Tells this helper to wait for FinishRespondWith() to be called before
+  // Tells this worker to wait for FinishRespondWith() to be called before
   // providing the response to the fetch event.
   void DeferResponse() { response_mode_ = ResponseMode::kDeferredResponse; }
   void FinishRespondWith() {
@@ -275,25 +281,23 @@
   }
 
  protected:
-  void OnFetchEvent(
-      int embedded_worker_id,
-      blink::mojom::FetchAPIRequestPtr request,
-      blink::mojom::FetchEventPreloadHandlePtr preload_handle,
+  void DispatchFetchEvent(
+      blink::mojom::DispatchFetchEventParamsPtr params,
       blink::mojom::ServiceWorkerFetchResponseCallbackPtr response_callback,
       blink::mojom::ServiceWorker::DispatchFetchEventCallback finish_callback)
       override {
     // Basic checks on DispatchFetchEvent parameters.
-    EXPECT_TRUE(request->is_main_resource_load);
+    EXPECT_TRUE(params->request->is_main_resource_load);
 
     has_received_fetch_event_ = true;
-    if (request->body)
-      request_body_ = request->body;
+    if (params->request->body)
+      request_body_ = params->request->body;
 
     switch (response_mode_) {
       case ResponseMode::kDefault:
-        EmbeddedWorkerTestHelper::OnFetchEvent(
-            embedded_worker_id, std::move(request), std::move(preload_handle),
-            std::move(response_callback), std::move(finish_callback));
+        FakeServiceWorker::DispatchFetchEvent(std::move(params),
+                                              std::move(response_callback),
+                                              std::move(finish_callback));
         break;
       case ResponseMode::kBlob:
         response_callback->OnResponse(
@@ -324,7 +328,7 @@
         break;
       case ResponseMode::kNavigationPreloadResponse:
         // Deletes itself when done.
-        new NavigationPreloadLoaderClient(std::move(preload_handle),
+        new NavigationPreloadLoaderClient(std::move(params->preload_handle),
                                           std::move(response_callback),
                                           std::move(finish_callback));
         break;
@@ -333,7 +337,8 @@
         // This causes ServiceWorkerVersion::StartRequest() to call its error
         // callback, which triggers ServiceWorkerNavigationLoader's dispatch
         // failed behavior.
-        SimulateWorkerStopped(embedded_worker_id);
+        embedded_worker_instance_client_->host()->OnStopped();
+
         // Finish the event by calling |finish_callback|.
         // This is the Mojo callback for
         // blink::mojom::ServiceWorker::DispatchFetchEvent().
@@ -401,7 +406,9 @@
   bool has_received_fetch_event_ = false;
   base::OnceClosure quit_closure_for_fetch_event_;
 
-  DISALLOW_COPY_AND_ASSIGN(Helper);
+  FakeEmbeddedWorkerInstanceClient* const embedded_worker_instance_client_;
+
+  DISALLOW_COPY_AND_ASSIGN(FetchEventServiceWorker);
 };
 
 // Returns typical response info for a resource load that went through a service
@@ -443,7 +450,7 @@
 
   void SetUp() override {
     feature_list_.InitAndEnableFeature(network::features::kNetworkService);
-    helper_ = std::make_unique<Helper>();
+    helper_ = std::make_unique<EmbeddedWorkerTestHelper>(base::FilePath());
 
     // Create an active service worker.
     storage()->LazyInitializeForTest(base::DoNothing());
@@ -474,6 +481,15 @@
                                  CreateReceiverOnCurrentThread(&status));
     base::RunLoop().RunUntilIdle();
     ASSERT_EQ(blink::ServiceWorkerStatusCode::kOk, status.value());
+
+    // Set up custom fakes to let tests customize how to respond to fetch
+    // events.
+    auto* client =
+        helper_->AddNewPendingInstanceClient<FakeEmbeddedWorkerInstanceClient>(
+            helper_.get());
+    service_worker_ =
+        helper_->AddNewPendingServiceWorker<FetchEventServiceWorker>(
+            helper_.get(), client);
   }
 
   ServiceWorkerStorage* storage() { return helper_->context()->storage(); }
@@ -591,9 +607,10 @@
   // --------------------------------------------------------------------------
 
   TestBrowserThreadBundle thread_bundle_;
-  std::unique_ptr<Helper> helper_;
+  std::unique_ptr<EmbeddedWorkerTestHelper> helper_;
   scoped_refptr<ServiceWorkerRegistration> registration_;
   scoped_refptr<ServiceWorkerVersion> version_;
+  FetchEventServiceWorker* service_worker_;
   storage::BlobStorageContext blob_context_;
   network::TestURLLoaderClient client_;
   bool was_main_resource_load_failed_called_ = false;
@@ -673,7 +690,7 @@
 
   // Verify that the request body was passed to the fetch event.
   std::string body;
-  helper_->ReadRequestBody(&body);
+  service_worker_->ReadRequestBody(&body);
   EXPECT_EQ(kData, body);
 }
 
@@ -692,7 +709,7 @@
   blob->size = blob_handle->size();
   blink::mojom::BlobRequest request = mojo::MakeRequest(&blob->blob);
   storage::BlobImpl::Create(std::move(blob_handle), std::move(request));
-  helper_->RespondWithBlob(std::move(blob));
+  service_worker_->RespondWithBlob(std::move(blob));
 
   // Perform the request.
   LoaderResult result = StartRequest(CreateRequest());
@@ -733,7 +750,7 @@
   blob->uuid = kBrokenUUID;
   blink::mojom::BlobRequest request = mojo::MakeRequest(&blob->blob);
   storage::BlobImpl::Create(std::move(blob_handle), std::move(request));
-  helper_->RespondWithBlob(std::move(blob));
+  service_worker_->RespondWithBlob(std::move(blob));
 
   // Perform the request.
   LoaderResult result = StartRequest(CreateRequest());
@@ -768,8 +785,8 @@
   const char kResponseBody[] = "Here is sample text for the Stream.";
   blink::mojom::ServiceWorkerStreamCallbackPtr stream_callback;
   mojo::DataPipe data_pipe;
-  helper_->RespondWithStream(mojo::MakeRequest(&stream_callback),
-                             std::move(data_pipe.consumer_handle));
+  service_worker_->RespondWithStream(mojo::MakeRequest(&stream_callback),
+                                     std::move(data_pipe.consumer_handle));
 
   // Perform the request.
   LoaderResult result = StartRequest(CreateRequest());
@@ -816,8 +833,8 @@
   const char kResponseBody[] = "Here is sample text for the Stream.";
   blink::mojom::ServiceWorkerStreamCallbackPtr stream_callback;
   mojo::DataPipe data_pipe;
-  helper_->RespondWithStream(mojo::MakeRequest(&stream_callback),
-                             std::move(data_pipe.consumer_handle));
+  service_worker_->RespondWithStream(mojo::MakeRequest(&stream_callback),
+                                     std::move(data_pipe.consumer_handle));
 
   // Perform the request.
   LoaderResult result = StartRequest(CreateRequest());
@@ -866,8 +883,8 @@
   const char kResponseBody[] = "Here is sample text for the Stream.";
   blink::mojom::ServiceWorkerStreamCallbackPtr stream_callback;
   mojo::DataPipe data_pipe;
-  helper_->RespondWithStream(mojo::MakeRequest(&stream_callback),
-                             std::move(data_pipe.consumer_handle));
+  service_worker_->RespondWithStream(mojo::MakeRequest(&stream_callback),
+                                     std::move(data_pipe.consumer_handle));
 
   // Perform the request.
   LoaderResult result = StartRequest(CreateRequest());
@@ -919,7 +936,7 @@
 // i.e., does not call respondWith().
 TEST_F(ServiceWorkerNavigationLoaderTest, FallbackResponse) {
   base::HistogramTester histogram_tester;
-  helper_->RespondWithFallback();
+  service_worker_->RespondWithFallback();
 
   // Perform the request.
   LoaderResult result = StartRequest(CreateRequest());
@@ -946,7 +963,7 @@
 // Test when the service worker rejects the FetchEvent.
 TEST_F(ServiceWorkerNavigationLoaderTest, ErrorResponse) {
   base::HistogramTester histogram_tester;
-  helper_->RespondWithError();
+  service_worker_->RespondWithError();
 
   // Perform the request.
   LoaderResult result = StartRequest(CreateRequest());
@@ -968,7 +985,7 @@
 // Test when dispatching the fetch event to the service worker failed.
 TEST_F(ServiceWorkerNavigationLoaderTest, FailFetchDispatch) {
   base::HistogramTester histogram_tester;
-  helper_->FailToDispatchFetchEvent();
+  service_worker_->FailToDispatchFetchEvent();
 
   // Perform the request.
   LoaderResult result = StartRequest(CreateRequest());
@@ -992,7 +1009,7 @@
 // Test when the respondWith() promise resolves before the waitUntil() promise
 // resolves. The response should be received before the event finishes.
 TEST_F(ServiceWorkerNavigationLoaderTest, EarlyResponse) {
-  helper_->RespondEarly();
+  service_worker_->RespondEarly();
 
   // Perform the request.
   LoaderResult result = StartRequest(CreateRequest());
@@ -1006,7 +1023,7 @@
   // Although the response was already received, the event remains outstanding
   // until waitUntil() resolves.
   EXPECT_TRUE(HasWorkInBrowser(version_.get()));
-  helper_->FinishWaitUntil();
+  service_worker_->FinishWaitUntil();
   EXPECT_FALSE(HasWorkInBrowser(version_.get()));
 }
 
@@ -1049,7 +1066,7 @@
 // Test responding to the fetch event with the navigation preload response.
 TEST_F(ServiceWorkerNavigationLoaderTest, NavigationPreload) {
   registration_->EnableNavigationPreload(true);
-  helper_->RespondWithNavigationPreloadResponse();
+  service_worker_->RespondWithNavigationPreloadResponse();
 
   // Perform the request
   LoaderResult result = StartRequest(CreateRequest());
@@ -1076,7 +1093,7 @@
 TEST_F(ServiceWorkerNavigationLoaderTest, Redirect) {
   base::HistogramTester histogram_tester;
   GURL new_url("https://example.com/redirected");
-  helper_->RespondWithRedirectResponse(new_url);
+  service_worker_->RespondWithRedirectResponse(new_url);
 
   // Perform the request.
   LoaderResult result = StartRequest(CreateRequest());
@@ -1151,12 +1168,12 @@
 }
 
 TEST_F(ServiceWorkerNavigationLoaderTest, ConnectionErrorDuringFetchEvent) {
-  helper_->DeferResponse();
+  service_worker_->DeferResponse();
   LoaderResult result = StartRequest(CreateRequest());
   EXPECT_EQ(LoaderResult::kHandledRequest, result);
 
   // Wait for the fetch event to be dispatched.
-  helper_->RunUntilFetchEvent();
+  service_worker_->RunUntilFetchEvent();
 
   // Break the Mojo connection. The loader should return an aborted status.
   loader_ptr_.reset();
@@ -1165,7 +1182,8 @@
 
   // The loader is still alive. Finish the fetch event. It shouldn't crash or
   // call any callbacks on |client_|, which would throw an error.
-  helper_->FinishRespondWith();
+  service_worker_->FinishRespondWith();
+
   // There's no event to wait for, so just pump the message loop and the test
   // passes if there is no error or crash.
   base::RunLoop().RunUntilIdle();
diff --git a/content/browser/service_worker/service_worker_object_host_unittest.cc b/content/browser/service_worker/service_worker_object_host_unittest.cc
index 25aeb8f..54c9c30 100644
--- a/content/browser/service_worker/service_worker_object_host_unittest.cc
+++ b/content/browser/service_worker/service_worker_object_host_unittest.cc
@@ -12,6 +12,7 @@
 #include "base/test/simple_test_tick_clock.h"
 #include "content/browser/service_worker/embedded_worker_registry.h"
 #include "content/browser/service_worker/embedded_worker_test_helper.h"
+#include "content/browser/service_worker/fake_embedded_worker_instance_client.h"
 #include "content/browser/service_worker/service_worker_context_core.h"
 #include "content/browser/service_worker/service_worker_dispatcher_host.h"
 #include "content/browser/service_worker/service_worker_object_host.h"
@@ -68,26 +69,15 @@
   std::vector<blink::mojom::ExtendableMessageEventPtr> events_;
 };
 
-class FailToStartWorkerTestHelper : public ExtendableMessageEventTestHelper {
+// An instance client that breaks the Mojo connection upon receiving the
+// Start() message.
+class FailStartInstanceClient : public FakeEmbeddedWorkerInstanceClient {
  public:
-  FailToStartWorkerTestHelper() : ExtendableMessageEventTestHelper() {}
+  FailStartInstanceClient(EmbeddedWorkerTestHelper* helper)
+      : FakeEmbeddedWorkerInstanceClient(helper) {}
 
-  void OnStartWorker(
-      int embedded_worker_id,
-      int64_t service_worker_version_id,
-      const GURL& scope,
-      const GURL& script_url,
-      bool pause_after_download,
-      blink::mojom::ServiceWorkerRequest service_worker_request,
-      blink::mojom::ControllerServiceWorkerRequest controller_request,
-      blink::mojom::EmbeddedWorkerInstanceHostAssociatedPtrInfo instance_host,
-      blink::mojom::ServiceWorkerProviderInfoForStartWorkerPtr provider_info,
-      blink::mojom::ServiceWorkerInstalledScriptsInfoPtr installed_scripts_info)
-      override {
-    blink::mojom::EmbeddedWorkerInstanceHostAssociatedPtr instance_host_ptr;
-    instance_host_ptr.Bind(std::move(instance_host));
-    instance_host_ptr->OnStopped();
-    base::RunLoop().RunUntilIdle();
+  void StartWorker(blink::mojom::EmbeddedWorkerStartParamsPtr params) override {
+    // Don't save the Mojo ptrs. The connection breaks.
   }
 };
 
@@ -380,7 +370,7 @@
   const int64_t kProviderId = 99;
   const GURL scope("https://www.example.com/");
   const GURL script_url("https://www.example.com/service_worker.js");
-  Initialize(std::make_unique<FailToStartWorkerTestHelper>());
+  Initialize(std::make_unique<ExtendableMessageEventTestHelper>());
   SetUpRegistration(scope, script_url);
 
   // Prepare a ServiceWorkerProviderHost for a window client. A
@@ -405,6 +395,10 @@
   ServiceWorkerObjectHost* object_host =
       GetServiceWorkerObjectHost(provider_host.get(), version_->version_id());
 
+  // Set up the service worker to fail to start.
+  helper_->AddPendingInstanceClient(
+      std::make_unique<FailStartInstanceClient>(helper_.get()));
+
   // Try to dispatch ExtendableMessageEvent. This should fail to start the
   // worker and to dispatch the event.
   blink::TransferableMessage message;
diff --git a/content/browser/service_worker/service_worker_registration_unittest.cc b/content/browser/service_worker/service_worker_registration_unittest.cc
index ac051996..00f439b 100644
--- a/content/browser/service_worker/service_worker_registration_unittest.cc
+++ b/content/browser/service_worker/service_worker_registration_unittest.cc
@@ -16,15 +16,21 @@
 #include "base/memory/weak_ptr.h"
 #include "base/run_loop.h"
 #include "base/test/simple_test_tick_clock.h"
+#include "base/test/test_simple_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "content/browser/service_worker/embedded_worker_status.h"
 #include "content/browser/service_worker/embedded_worker_test_helper.h"
+#include "content/browser/service_worker/fake_embedded_worker_instance_client.h"
+#include "content/browser/service_worker/fake_service_worker.h"
 #include "content/browser/service_worker/service_worker_context_core.h"
+#include "content/browser/service_worker/service_worker_context_core_observer.h"
+#include "content/browser/service_worker/service_worker_context_wrapper.h"
 #include "content/browser/service_worker/service_worker_dispatcher_host.h"
 #include "content/browser/service_worker/service_worker_provider_host.h"
 #include "content/browser/service_worker/service_worker_registration_object_host.h"
 #include "content/browser/service_worker/service_worker_test_utils.h"
+#include "content/browser/service_worker/test_service_worker_observer.h"
 #include "content/common/service_worker/service_worker_utils.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "content/test/test_content_browser_client.h"
@@ -41,10 +47,26 @@
 // From service_worker_registration.cc.
 constexpr base::TimeDelta kMaxLameDuckTime = base::TimeDelta::FromMinutes(5);
 
+// TODO(falken): Make this a common helper function.
+void StartWorker(ServiceWorkerVersion* version,
+                 ServiceWorkerMetrics::EventType purpose) {
+  base::RunLoop loop;
+  blink::ServiceWorkerStatusCode code;
+  version->StartWorker(
+      purpose,
+      base::BindOnce(
+          [](base::OnceClosure done, blink::ServiceWorkerStatusCode* out_code,
+             blink::ServiceWorkerStatusCode result_code) {
+            *out_code = result_code;
+            std::move(done).Run();
+          },
+          loop.QuitClosure(), &code));
+  loop.Run();
+  EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, code);
+}
+
 int CreateInflightRequest(ServiceWorkerVersion* version) {
-  version->StartWorker(ServiceWorkerMetrics::EventType::PUSH,
-                       base::DoNothing());
-  base::RunLoop().RunUntilIdle();
+  StartWorker(version, ServiceWorkerMetrics::EventType::PUSH);
   return version->StartRequest(ServiceWorkerMetrics::EventType::PUSH,
                                base::DoNothing());
 }
@@ -67,6 +89,29 @@
   }
 };
 
+void RequestTermination(
+    blink::mojom::EmbeddedWorkerInstanceHostAssociatedPtr* host) {
+  // We can't wait for the callback since Stop() arrives first which severs
+  // the connection.
+  (*host)->RequestTermination(base::DoNothing());
+}
+
+class ActivationTestServiceWorker : public FakeServiceWorker {
+ public:
+  explicit ActivationTestServiceWorker(EmbeddedWorkerTestHelper* helper)
+      : FakeServiceWorker(helper) {}
+  ~ActivationTestServiceWorker() override = default;
+
+  bool is_zero_idle_timer_delay() const { return is_zero_idle_timer_delay_; }
+
+  void SetIdleTimerDelayToZero() override { is_zero_idle_timer_delay_ = true; }
+
+ private:
+  bool is_zero_idle_timer_delay_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(ActivationTestServiceWorker);
+};
+
 class MockServiceWorkerRegistrationObject
     : public blink::mojom::ServiceWorkerRegistrationObject {
  public:
@@ -228,8 +273,8 @@
   };
 
  protected:
-  std::unique_ptr<RegistrationTestHelper> helper_;
   TestBrowserThreadBundle thread_bundle_;
+  std::unique_ptr<RegistrationTestHelper> helper_;
 };
 
 TEST_F(ServiceWorkerRegistrationTest, SetAndUnsetVersions) {
@@ -426,7 +471,16 @@
     DCHECK(remote_endpoint_.host_ptr()->is_bound());
     version_1->AddControllee(host_.get());
 
-    // Give the active version an in-flight request.
+    // Setup the Mojo implementation fakes for the renderer-side service worker.
+    // These will be bound once the service worker starts.
+    version_1_client_ =
+        helper_->AddNewPendingInstanceClient<FakeEmbeddedWorkerInstanceClient>(
+            helper_.get());
+    version_1_service_worker_ =
+        helper_->AddNewPendingServiceWorker<ActivationTestServiceWorker>(
+            helper_.get());
+
+    // Start the active version and give it an in-flight request.
     inflight_request_id_ = CreateInflightRequest(version_1.get());
 
     // Create a waiting version.
@@ -445,8 +499,18 @@
     version_2->set_fetch_handler_existence(
         ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
     registration_->SetWaitingVersion(version_2);
-    version_2->StartWorker(ServiceWorkerMetrics::EventType::INSTALL,
-                           base::DoNothing());
+
+    // Setup the Mojo implementation fakes for the renderer-side service worker.
+    // These will be bound once the service worker starts.
+    version_2_client_ =
+        helper_->AddNewPendingInstanceClient<FakeEmbeddedWorkerInstanceClient>(
+            helper_.get());
+    version_2_service_worker_ =
+        helper_->AddNewPendingServiceWorker<ActivationTestServiceWorker>(
+            helper_.get());
+
+    // Start the worker.
+    StartWorker(version_2.get(), ServiceWorkerMetrics::EventType::INSTALL);
     version_2->SetStatus(ServiceWorkerVersion::INSTALLED);
 
     // Set it to activate when ready. The original version should still be
@@ -502,11 +566,34 @@
     base::RunLoop().RunUntilIdle();
   }
 
+  FakeEmbeddedWorkerInstanceClient* version_1_client() {
+    return version_1_client_;
+  }
+  FakeEmbeddedWorkerInstanceClient* version_2_client() {
+    return version_2_client_;
+  }
+  ActivationTestServiceWorker* version_1_service_worker() {
+    return version_1_service_worker_;
+  }
+  ActivationTestServiceWorker* version_2_service_worker() {
+    return version_2_service_worker_;
+  }
+
  private:
   scoped_refptr<ServiceWorkerRegistration> registration_;
+
+  // Mojo implementation fakes for the renderer-side service workers. Their
+  // lifetime is bound to the Mojo connection.
+  FakeEmbeddedWorkerInstanceClient* version_1_client_ = nullptr;
+  ActivationTestServiceWorker* version_1_service_worker_ = nullptr;
+  FakeEmbeddedWorkerInstanceClient* version_2_client_ = nullptr;
+  ActivationTestServiceWorker* version_2_service_worker_ = nullptr;
+
   std::unique_ptr<ServiceWorkerProviderHost> host_;
   ServiceWorkerRemoteProviderEndpoint remote_endpoint_;
   int inflight_request_id_ = -1;
+
+  DISALLOW_COPY_AND_ASSIGN(ServiceWorkerActivationTest);
 };
 
 // Test activation triggered by finishing all requests.
@@ -514,6 +601,8 @@
   scoped_refptr<ServiceWorkerRegistration> reg = registration();
   scoped_refptr<ServiceWorkerVersion> version_1 = reg->active_version();
   scoped_refptr<ServiceWorkerVersion> version_2 = reg->waiting_version();
+  auto runner = base::MakeRefCounted<base::TestSimpleTaskRunner>();
+  reg->SetTaskRunnerForTest(runner);
 
   // Remove the controllee. Since there is an in-flight request,
   // activation should not yet happen.
@@ -523,20 +612,16 @@
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(version_1.get(), reg->active_version());
   if (blink::ServiceWorkerUtils::IsServicificationEnabled())
-    EXPECT_TRUE(helper_->is_zero_idle_timer_delay());
+    EXPECT_TRUE(version_1_service_worker()->is_zero_idle_timer_delay());
 
   // Finish the request. Activation should happen.
   version_1->FinishRequest(inflight_request_id(), true /* was_handled */);
-  base::RunLoop().RunUntilIdle();
-
   if (blink::ServiceWorkerUtils::IsServicificationEnabled()) {
     EXPECT_EQ(version_1.get(), reg->active_version());
-    helper_->RequestTermination(
-        version_1->embedded_worker()->embedded_worker_id());
-    base::RunLoop().RunUntilIdle();
-    EXPECT_TRUE(helper_->will_be_terminated().value());
+    RequestTermination(&version_1_client()->host());
   }
-
+  TestServiceWorkerObserver observer(helper_->context_wrapper());
+  observer.RunUntilActivated(version_2.get(), runner);
   EXPECT_EQ(version_2.get(), reg->active_version());
 }
 
@@ -577,7 +662,7 @@
   EXPECT_FALSE(result.has_value());
   EXPECT_EQ(version_1.get(), reg->active_version());
   if (blink::ServiceWorkerUtils::IsServicificationEnabled())
-    EXPECT_TRUE(helper_->is_zero_idle_timer_delay());
+    EXPECT_TRUE(version_1_service_worker()->is_zero_idle_timer_delay());
 
   // Finish the request.
   // non-S13nServiceWorker: The service worker becomes idle.
@@ -588,10 +673,7 @@
 
   if (blink::ServiceWorkerUtils::IsServicificationEnabled()) {
     EXPECT_EQ(version_1.get(), reg->active_version());
-    helper_->RequestTermination(
-        version_1->embedded_worker()->embedded_worker_id());
-    base::RunLoop().RunUntilIdle();
-    EXPECT_TRUE(helper_->will_be_terminated().value());
+    RequestTermination(&version_1_client()->host());
   }
 
   // Wait until SkipWaiting resolves.
@@ -624,13 +706,10 @@
                                   skip_waiting_loop.QuitClosure());
 
   if (blink::ServiceWorkerUtils::IsServicificationEnabled()) {
-    EXPECT_TRUE(helper_->is_zero_idle_timer_delay());
+    EXPECT_TRUE(version_1_service_worker()->is_zero_idle_timer_delay());
     EXPECT_FALSE(result.has_value());
     EXPECT_EQ(version_1.get(), reg->active_version());
-    helper_->RequestTermination(
-        version_1->embedded_worker()->embedded_worker_id());
-    base::RunLoop().RunUntilIdle();
-    EXPECT_TRUE(helper_->will_be_terminated().value());
+    RequestTermination(&version_1_client()->host());
   }
 
   // Wait until SkipWaiting resolves.
diff --git a/content/browser/service_worker/service_worker_url_request_job.h b/content/browser/service_worker/service_worker_url_request_job.h
index 6384c93..5f0ba12c 100644
--- a/content/browser/service_worker/service_worker_url_request_job.h
+++ b/content/browser/service_worker/service_worker_url_request_job.h
@@ -65,6 +65,7 @@
 }  // namespace service_worker_controllee_request_handler_unittest
 
 namespace service_worker_url_request_job_unittest {
+class ServiceWorkerURLRequestJobTest;
 class DelayHelper;
 }  // namespace service_worker_url_request_job_unittest
 
@@ -151,7 +152,8 @@
 
   class NavigationPreloadMetrics;
   friend class service_worker_url_request_job_unittest::DelayHelper;
-  friend class ServiceWorkerURLRequestJobTest;
+  friend class service_worker_url_request_job_unittest::
+      ServiceWorkerURLRequestJobTest;
   FRIEND_TEST_ALL_PREFIXES(service_worker_controllee_request_handler_unittest::
                                ServiceWorkerControlleeRequestHandlerTest,
                            LostActiveVersion);
diff --git a/content/browser/service_worker/service_worker_url_request_job_unittest.cc b/content/browser/service_worker/service_worker_url_request_job_unittest.cc
index 10abbb5..52a5f8a0 100644
--- a/content/browser/service_worker/service_worker_url_request_job_unittest.cc
+++ b/content/browser/service_worker/service_worker_url_request_job_unittest.cc
@@ -26,6 +26,8 @@
 #include "content/browser/resource_context_impl.h"
 #include "content/browser/service_worker/embedded_worker_registry.h"
 #include "content/browser/service_worker/embedded_worker_test_helper.h"
+#include "content/browser/service_worker/fake_embedded_worker_instance_client.h"
+#include "content/browser/service_worker/fake_service_worker.h"
 #include "content/browser/service_worker/service_worker_context_core.h"
 #include "content/browser/service_worker/service_worker_context_wrapper.h"
 #include "content/browser/service_worker/service_worker_provider_host.h"
@@ -419,6 +421,10 @@
   }
   // ---------------------------------------------------------------------------
 
+  void CompleteNavigationPreload() {
+    handler()->job()->OnNavigationPreloadResponse();
+  }
+
   // |scoped_feature_list_| must be before |thread_bundle_|.
   // See comments in ServiceWorkerProviderHostTest.
   base::test::ScopedFeatureList scoped_feature_list_;
@@ -470,93 +476,47 @@
   EXPECT_EQ(std::string(), info->response_cache_storage_cache_name());
 }
 
-// Helper for controlling when to start a worker and respond to a fetch event.
-class DelayHelper : public EmbeddedWorkerTestHelper {
+// For controlling when to respond to a fetch event.
+class DelayFetchWorker : public FakeServiceWorker {
  public:
-  DelayHelper(ServiceWorkerURLRequestJobTest* test)
-      : EmbeddedWorkerTestHelper(base::FilePath()), test_(test) {}
-  ~DelayHelper() override {}
+  explicit DelayFetchWorker(EmbeddedWorkerTestHelper* helper)
+      : FakeServiceWorker(std::move(helper)) {}
+  ~DelayFetchWorker() override = default;
 
-  void CompleteNavigationPreload() {
-    test_->handler()->job()->OnNavigationPreloadResponse();
-  }
-
-  void CompleteStartWorker() {
-    EmbeddedWorkerTestHelper::OnStartWorker(
-        embedded_worker_id_, service_worker_version_id_, scope_, script_url_,
-        pause_after_download_, std::move(start_worker_request_),
-        std::move(controller_request_), std::move(start_worker_instance_host_),
-        std::move(provider_info_), std::move(installed_scripts_info_));
+  void DispatchFetchEvent(
+      blink::mojom::DispatchFetchEventParamsPtr params,
+      blink::mojom::ServiceWorkerFetchResponseCallbackPtr response_callback,
+      DispatchFetchEventCallback callback) override {
+    params_ = std::move(params);
+    response_callback_ = std::move(response_callback);
+    callback_ = std::move(callback);
+    if (respond_immediately_)
+      Respond();
   }
 
   void Respond() {
-    response_callback_->OnResponse(
-        MakeOkResponse(), blink::mojom::ServiceWorkerFetchEventTiming::New());
-    std::move(finish_callback_)
-        .Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED);
-  }
-
- protected:
-  void OnStartWorker(
-      int embedded_worker_id,
-      int64_t service_worker_version_id,
-      const GURL& scope,
-      const GURL& script_url,
-      bool pause_after_download,
-      blink::mojom::ServiceWorkerRequest service_worker_request,
-      blink::mojom::ControllerServiceWorkerRequest controller_request,
-      blink::mojom::EmbeddedWorkerInstanceHostAssociatedPtrInfo instance_host,
-      blink::mojom::ServiceWorkerProviderInfoForStartWorkerPtr provider_info,
-      blink::mojom::ServiceWorkerInstalledScriptsInfoPtr installed_scripts_info)
-      override {
-    embedded_worker_id_ = embedded_worker_id;
-    service_worker_version_id_ = service_worker_version_id;
-    scope_ = scope;
-    script_url_ = script_url;
-    pause_after_download_ = pause_after_download;
-    start_worker_request_ = std::move(service_worker_request);
-    controller_request_ = std::move(controller_request);
-    start_worker_instance_host_ = std::move(instance_host);
-    provider_info_ = std::move(provider_info);
-    installed_scripts_info_ = std::move(installed_scripts_info);
-  }
-
-  void OnFetchEvent(
-      int embedded_worker_id,
-      blink::mojom::FetchAPIRequestPtr /* request */,
-      blink::mojom::FetchEventPreloadHandlePtr preload_handle,
-      blink::mojom::ServiceWorkerFetchResponseCallbackPtr response_callback,
-      blink::mojom::ServiceWorker::DispatchFetchEventCallback finish_callback)
-      override {
-    embedded_worker_id_ = embedded_worker_id;
-    response_callback_ = std::move(response_callback);
-    finish_callback_ = std::move(finish_callback);
-    preload_handle_ = std::move(preload_handle);
+    if (!params_) {
+      respond_immediately_ = true;
+      return;
+    }
+    FakeServiceWorker::DispatchFetchEvent(std::move(params_),
+                                          std::move(response_callback_),
+                                          std::move(callback_));
   }
 
  private:
-  int64_t service_worker_version_id_;
-  GURL scope_;
-  GURL script_url_;
-  bool pause_after_download_;
-  blink::mojom::ServiceWorkerRequest start_worker_request_;
-  blink::mojom::ControllerServiceWorkerRequest controller_request_;
-  blink::mojom::EmbeddedWorkerInstanceHostAssociatedPtrInfo
-      start_worker_instance_host_;
-  blink::mojom::ServiceWorkerProviderInfoForStartWorkerPtr provider_info_;
-  blink::mojom::ServiceWorkerInstalledScriptsInfoPtr installed_scripts_info_;
-  int embedded_worker_id_ = 0;
+  bool respond_immediately_ = false;
+  blink::mojom::DispatchFetchEventParamsPtr params_;
   blink::mojom::ServiceWorkerFetchResponseCallbackPtr response_callback_;
-  blink::mojom::FetchEventPreloadHandlePtr preload_handle_;
-  blink::mojom::ServiceWorker::DispatchFetchEventCallback finish_callback_;
-  ServiceWorkerURLRequestJobTest* test_;
-  DISALLOW_COPY_AND_ASSIGN(DelayHelper);
+  DispatchFetchEventCallback callback_;
+  DISALLOW_COPY_AND_ASSIGN(DelayFetchWorker);
 };
 
 TEST_F(ServiceWorkerURLRequestJobTest,
        NavPreloadMetrics_WorkerAlreadyStarted_MainFrame) {
-  SetUpWithHelper(std::make_unique<DelayHelper>(this));
-  DelayHelper* helper = static_cast<DelayHelper*>(helper_.get());
+  SetUpWithHelper(std::make_unique<EmbeddedWorkerTestHelper>(base::FilePath()));
+  auto* worker =
+      helper_->AddNewPendingServiceWorker<DelayFetchWorker>(helper_.get());
 
   // Start the worker before the navigation.
   blink::ServiceWorkerStatusCode status =
@@ -565,14 +525,12 @@
   version_->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
                         base::BindOnce(&SaveStatusCallback, &status));
   base::RunLoop().RunUntilIdle();
-  helper->CompleteStartWorker();
-  base::RunLoop().RunUntilIdle();
   EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status);
 
   // Do the navigation.
   SetUpNavigationPreloadTest(RESOURCE_TYPE_MAIN_FRAME);
-  helper->CompleteNavigationPreload();
-  helper->Respond();
+  CompleteNavigationPreload();
+  worker->Respond();
   base::RunLoop().RunUntilIdle();
 
   histogram_tester.ExpectUniqueSample(
@@ -590,16 +548,20 @@
 
 TEST_F(ServiceWorkerURLRequestJobTest,
        NavPreloadMetrics_WorkerFirst_MainFrame) {
-  SetUpWithHelper(std::make_unique<DelayHelper>(this));
-  DelayHelper* helper = static_cast<DelayHelper*>(helper_.get());
+  SetUpWithHelper(std::make_unique<EmbeddedWorkerTestHelper>(base::FilePath()));
+  auto* client = helper_->AddNewPendingInstanceClient<
+      DelayedFakeEmbeddedWorkerInstanceClient>(helper_.get());
+  auto* worker =
+      helper_->AddNewPendingServiceWorker<DelayFetchWorker>(helper_.get());
 
   base::HistogramTester histogram_tester;
   SetUpNavigationPreloadTest(RESOURCE_TYPE_MAIN_FRAME);
 
   // Worker finishes first.
-  helper->CompleteStartWorker();
-  helper->CompleteNavigationPreload();
-  helper->Respond();
+  client->UnblockStartWorker();
+  base::RunLoop().RunUntilIdle();
+  CompleteNavigationPreload();
+  worker->Respond();
   base::RunLoop().RunUntilIdle();
 
   histogram_tester.ExpectUniqueSample(
@@ -617,16 +579,19 @@
 
 TEST_F(ServiceWorkerURLRequestJobTest,
        NavPreloadMetrics_NavPreloadFirst_MainFrame) {
-  SetUpWithHelper(std::make_unique<DelayHelper>(this));
-  DelayHelper* helper = static_cast<DelayHelper*>(helper_.get());
+  SetUpWithHelper(std::make_unique<EmbeddedWorkerTestHelper>(base::FilePath()));
+  auto* client = helper_->AddNewPendingInstanceClient<
+      DelayedFakeEmbeddedWorkerInstanceClient>(helper_.get());
+  auto* worker =
+      helper_->AddNewPendingServiceWorker<DelayFetchWorker>(helper_.get());
 
   base::HistogramTester histogram_tester;
   SetUpNavigationPreloadTest(RESOURCE_TYPE_MAIN_FRAME);
 
   // Nav preload finishes first.
-  helper->CompleteNavigationPreload();
-  helper->CompleteStartWorker();
-  helper->Respond();
+  CompleteNavigationPreload();
+  client->UnblockStartWorker();
+  worker->Respond();
   base::RunLoop().RunUntilIdle();
 
   histogram_tester.ExpectUniqueSample(
@@ -643,16 +608,20 @@
 }
 
 TEST_F(ServiceWorkerURLRequestJobTest, NavPreloadMetrics_WorkerFirst_SubFrame) {
-  SetUpWithHelper(std::make_unique<DelayHelper>(this));
-  DelayHelper* helper = static_cast<DelayHelper*>(helper_.get());
+  SetUpWithHelper(std::make_unique<EmbeddedWorkerTestHelper>(base::FilePath()));
+  auto* client = helper_->AddNewPendingInstanceClient<
+      DelayedFakeEmbeddedWorkerInstanceClient>(helper_.get());
+  auto* worker =
+      helper_->AddNewPendingServiceWorker<DelayFetchWorker>(helper_.get());
 
   base::HistogramTester histogram_tester;
   SetUpNavigationPreloadTest(RESOURCE_TYPE_SUB_FRAME);
 
   // Worker finishes first.
-  helper->CompleteStartWorker();
-  helper->CompleteNavigationPreload();
-  helper->Respond();
+  client->UnblockStartWorker();
+  base::RunLoop().RunUntilIdle();
+  CompleteNavigationPreload();
+  worker->Respond();
   base::RunLoop().RunUntilIdle();
 
   histogram_tester.ExpectTotalCount(
@@ -669,16 +638,19 @@
 
 TEST_F(ServiceWorkerURLRequestJobTest,
        NavPreloadMetrics_NavPreloadFirst_SubFrame) {
-  SetUpWithHelper(std::make_unique<DelayHelper>(this));
-  DelayHelper* helper = static_cast<DelayHelper*>(helper_.get());
+  SetUpWithHelper(std::make_unique<EmbeddedWorkerTestHelper>(base::FilePath()));
+  auto* client = helper_->AddNewPendingInstanceClient<
+      DelayedFakeEmbeddedWorkerInstanceClient>(helper_.get());
+  auto* worker =
+      helper_->AddNewPendingServiceWorker<DelayFetchWorker>(helper_.get());
 
   base::HistogramTester histogram_tester;
   SetUpNavigationPreloadTest(RESOURCE_TYPE_SUB_FRAME);
 
   // Nav preload finishes first.
-  helper->CompleteNavigationPreload();
-  helper->CompleteStartWorker();
-  helper->Respond();
+  CompleteNavigationPreload();
+  client->UnblockStartWorker();
+  worker->Respond();
   base::RunLoop().RunUntilIdle();
 
   histogram_tester.ExpectTotalCount(
@@ -1255,32 +1227,38 @@
   EXPECT_FALSE(info->service_worker_ready_time().is_null());
 }
 
-// Helper to simulate failing to dispatch a fetch event to a worker.
-class FailFetchHelper : public EmbeddedWorkerTestHelper {
+// Simulates failing to dispatch a fetch event to a worker.
+class FailFetchWorker : public FakeServiceWorker {
  public:
-  FailFetchHelper() : EmbeddedWorkerTestHelper(base::FilePath()) {}
-  ~FailFetchHelper() override {}
+  explicit FailFetchWorker(EmbeddedWorkerTestHelper* helper,
+                           FakeEmbeddedWorkerInstanceClient* instance_client)
+      : FakeServiceWorker(helper), instance_client_(instance_client) {}
+  ~FailFetchWorker() override = default;
 
  protected:
-  void OnFetchEvent(
-      int embedded_worker_id,
-      blink::mojom::FetchAPIRequestPtr /* request */,
-      blink::mojom::FetchEventPreloadHandlePtr /* preload_handle */,
-      blink::mojom::
-          ServiceWorkerFetchResponseCallbackPtr /* response_callback */,
-      blink::mojom::ServiceWorker::DispatchFetchEventCallback finish_callback)
-      override {
-    SimulateWorkerStopped(embedded_worker_id);
-    std::move(finish_callback)
-        .Run(blink::mojom::ServiceWorkerEventStatus::ABORTED);
+  void DispatchFetchEvent(
+      blink::mojom::DispatchFetchEventParamsPtr params,
+      blink::mojom::ServiceWorkerFetchResponseCallbackPtr response_callback,
+      DispatchFetchEventCallback callback) override {
+    instance_client_->host()->OnStopped();
+    std::move(callback).Run(blink::mojom::ServiceWorkerEventStatus::ABORTED);
   }
 
  private:
-  DISALLOW_COPY_AND_ASSIGN(FailFetchHelper);
+  FakeEmbeddedWorkerInstanceClient* const instance_client_;
+  DISALLOW_COPY_AND_ASSIGN(FailFetchWorker);
 };
 
 TEST_F(ServiceWorkerURLRequestJobTest, FailFetchDispatch) {
-  SetUpWithHelper(std::make_unique<FailFetchHelper>());
+  SetUpWithHelper(std::make_unique<EmbeddedWorkerTestHelper>(base::FilePath()));
+
+  // Setup Mojo implementation fakes for the renderer-side service worker.
+  // These force dispatching the fetch event to fail.
+  auto* instance_client =
+      helper_->AddNewPendingInstanceClient<FakeEmbeddedWorkerInstanceClient>(
+          helper_.get());
+  helper_->AddPendingServiceWorker(
+      std::make_unique<FailFetchWorker>(helper_.get(), instance_client));
 
   version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
   request_ = url_request_context_.CreateRequest(
@@ -1403,8 +1381,11 @@
 
 // Test cancelling the URLRequest while the fetch event is in flight.
 TEST_F(ServiceWorkerURLRequestJobTest, CancelRequest) {
-  SetUpWithHelper(std::make_unique<DelayHelper>(this));
-  DelayHelper* helper = static_cast<DelayHelper*>(helper_.get());
+  SetUpWithHelper(std::make_unique<EmbeddedWorkerTestHelper>(base::FilePath()));
+  auto* client = helper_->AddNewPendingInstanceClient<
+      DelayedFakeEmbeddedWorkerInstanceClient>(helper_.get());
+  auto* worker =
+      helper_->AddNewPendingServiceWorker<DelayFetchWorker>(helper_.get());
 
   // Start the URL request. The job will be waiting for the
   // worker to respond to the fetch event.
@@ -1415,7 +1396,7 @@
   request_->set_method("GET");
   request_->Start();
   base::RunLoop().RunUntilIdle();
-  helper->CompleteStartWorker();
+  client->UnblockStartWorker();
   base::RunLoop().RunUntilIdle();
 
   // Cancel the URL request.
@@ -1424,7 +1405,7 @@
 
   // Respond to the fetch event.
   EXPECT_FALSE(version_->HasNoWork());
-  helper->Respond();
+  worker->Respond();
   base::RunLoop().RunUntilIdle();
 
   // The fetch event request should no longer be in-flight.
diff --git a/content/browser/service_worker/service_worker_version.h b/content/browser/service_worker/service_worker_version.h
index aebb003..f3192c6 100644
--- a/content/browser/service_worker/service_worker_version.h
+++ b/content/browser/service_worker/service_worker_version.h
@@ -73,19 +73,13 @@
 
 namespace service_worker_version_unittest {
 class ServiceWorkerVersionTest;
-class ServiceWorkerRequestTimeoutTest;
-class ServiceWorkerFailToStartTest;
-FORWARD_DECLARE_TEST(ServiceWorkerFailToStartTest, Timeout);
-FORWARD_DECLARE_TEST(ServiceWorkerRequestTimeoutTest, RequestTimeout);
-FORWARD_DECLARE_TEST(ServiceWorkerStallInStoppingTest, DetachThenRestart);
-FORWARD_DECLARE_TEST(ServiceWorkerStallInStoppingTest,
-                     DetachThenRestartNoCrash);
-FORWARD_DECLARE_TEST(ServiceWorkerStallInStoppingTest, DetachThenStart);
+FORWARD_DECLARE_TEST(ServiceWorkerVersionTest, FailToStart_Timeout);
 FORWARD_DECLARE_TEST(ServiceWorkerVersionTest, IdleTimeout);
 FORWARD_DECLARE_TEST(ServiceWorkerVersionTest, MixedRequestTimeouts);
 FORWARD_DECLARE_TEST(ServiceWorkerVersionTest, RegisterForeignFetchScopes);
 FORWARD_DECLARE_TEST(ServiceWorkerVersionTest, RequestCustomizedTimeout);
 FORWARD_DECLARE_TEST(ServiceWorkerVersionTest, RequestNowTimeout);
+FORWARD_DECLARE_TEST(ServiceWorkerVersionTest, RequestTimeout);
 FORWARD_DECLARE_TEST(ServiceWorkerVersionTest, RestartWorker);
 FORWARD_DECLARE_TEST(ServiceWorkerVersionTest, RequestNowTimeoutKill);
 FORWARD_DECLARE_TEST(ServiceWorkerVersionTest, SetDevToolsAttached);
@@ -94,6 +88,9 @@
 FORWARD_DECLARE_TEST(ServiceWorkerVersionTest, StaleUpdate_NonActiveWorker);
 FORWARD_DECLARE_TEST(ServiceWorkerVersionTest, StaleUpdate_RunningWorker);
 FORWARD_DECLARE_TEST(ServiceWorkerVersionTest, StaleUpdate_StartWorker);
+FORWARD_DECLARE_TEST(ServiceWorkerVersionTest,
+                     StallInStopping_DetachThenRestart);
+FORWARD_DECLARE_TEST(ServiceWorkerVersionTest, StallInStopping_DetachThenStart);
 FORWARD_DECLARE_TEST(ServiceWorkerVersionTest, StartRequestWithNullContext);
 }  // namespace service_worker_version_unittest
 
@@ -576,29 +573,26 @@
       service_worker_version_unittest::ServiceWorkerVersionTest,
       StartRequestWithNullContext);
   FRIEND_TEST_ALL_PREFIXES(
-      service_worker_version_unittest::ServiceWorkerRequestTimeoutTest,
-      RequestTimeout);
-  FRIEND_TEST_ALL_PREFIXES(
-      service_worker_version_unittest::ServiceWorkerFailToStartTest,
-      Timeout);
+      service_worker_version_unittest::ServiceWorkerVersionTest,
+      FailToStart_Timeout);
   FRIEND_TEST_ALL_PREFIXES(ServiceWorkerVersionBrowserTest,
                            TimeoutStartingWorker);
   FRIEND_TEST_ALL_PREFIXES(ServiceWorkerVersionBrowserTest,
                            TimeoutWorkerInEvent);
   FRIEND_TEST_ALL_PREFIXES(
-      service_worker_version_unittest::ServiceWorkerStallInStoppingTest,
-      DetachThenStart);
+      service_worker_version_unittest::ServiceWorkerVersionTest,
+      StallInStopping_DetachThenStart);
   FRIEND_TEST_ALL_PREFIXES(
-      service_worker_version_unittest::ServiceWorkerStallInStoppingTest,
-      DetachThenRestart);
-  FRIEND_TEST_ALL_PREFIXES(
-      service_worker_version_unittest::ServiceWorkerStallInStoppingTest,
-      DetachThenRestartNoCrash);
+      service_worker_version_unittest::ServiceWorkerVersionTest,
+      StallInStopping_DetachThenRestart);
   FRIEND_TEST_ALL_PREFIXES(
       service_worker_version_unittest::ServiceWorkerVersionTest,
       RequestNowTimeout);
   FRIEND_TEST_ALL_PREFIXES(
       service_worker_version_unittest::ServiceWorkerVersionTest,
+      RequestTimeout);
+  FRIEND_TEST_ALL_PREFIXES(
+      service_worker_version_unittest::ServiceWorkerVersionTest,
       RestartWorker);
   FRIEND_TEST_ALL_PREFIXES(
       service_worker_version_unittest::ServiceWorkerVersionTest,
diff --git a/content/browser/service_worker/service_worker_version_unittest.cc b/content/browser/service_worker/service_worker_version_unittest.cc
index 76fa74a..99d4df35 100644
--- a/content/browser/service_worker/service_worker_version_unittest.cc
+++ b/content/browser/service_worker/service_worker_version_unittest.cc
@@ -21,6 +21,8 @@
 #include "content/browser/service_worker/embedded_worker_registry.h"
 #include "content/browser/service_worker/embedded_worker_status.h"
 #include "content/browser/service_worker/embedded_worker_test_helper.h"
+#include "content/browser/service_worker/fake_embedded_worker_instance_client.h"
+#include "content/browser/service_worker/fake_service_worker.h"
 #include "content/browser/service_worker/service_worker_context_core.h"
 #include "content/browser/service_worker/service_worker_dispatcher_host.h"
 #include "content/browser/service_worker/service_worker_ping_controller.h"
@@ -43,24 +45,6 @@
 namespace content {
 namespace service_worker_version_unittest {
 
-class MessageReceiver : public EmbeddedWorkerTestHelper {
- public:
-  MessageReceiver() : EmbeddedWorkerTestHelper(base::FilePath()) {}
-  ~MessageReceiver() override {}
-
-  void SimulateSetCachedMetadata(int embedded_worker_id,
-                                 const GURL& url,
-                                 const std::vector<uint8_t>& data) {
-    GetServiceWorkerHost(embedded_worker_id)->SetCachedMetadata(url, data);
-  }
-
-  void SimulateClearCachedMetadata(int embedded_worker_id, const GURL& url) {
-    GetServiceWorkerHost(embedded_worker_id)->ClearCachedMetadata(url);
-  }
-
-  DISALLOW_COPY_AND_ASSIGN(MessageReceiver);
-};
-
 void VerifyCalled(bool* called) {
   *called = true;
 }
@@ -146,8 +130,7 @@
       : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {}
 
   void SetUp() override {
-    helper_ = GetMessageReceiver();
-
+    helper_ = GetHelper();
     helper_->context()->storage()->LazyInitializeForTest(base::DoNothing());
     base::RunLoop().RunUntilIdle();
 
@@ -183,8 +166,8 @@
     ASSERT_EQ(blink::ServiceWorkerStatusCode::kOk, status.value());
   }
 
-  virtual std::unique_ptr<MessageReceiver> GetMessageReceiver() {
-    return std::make_unique<MessageReceiver>();
+  virtual std::unique_ptr<EmbeddedWorkerTestHelper> GetHelper() {
+    return std::make_unique<EmbeddedWorkerTestHelper>(base::FilePath());
   }
 
   void TearDown() override {
@@ -250,7 +233,7 @@
   }
 
   TestBrowserThreadBundle thread_bundle_;
-  std::unique_ptr<MessageReceiver> helper_;
+  std::unique_ptr<EmbeddedWorkerTestHelper> helper_;
   scoped_refptr<ServiceWorkerRegistration> registration_;
   scoped_refptr<ServiceWorkerVersion> version_;
   GURL scope_;
@@ -259,142 +242,19 @@
   DISALLOW_COPY_AND_ASSIGN(ServiceWorkerVersionTest);
 };
 
-class MessageReceiverDisallowStart : public MessageReceiver {
+// An instance client that breaks the Mojo connection upon receiving the
+// Start() message.
+class FailStartInstanceClient : public FakeEmbeddedWorkerInstanceClient {
  public:
-  MessageReceiverDisallowStart() : MessageReceiver() {}
-  ~MessageReceiverDisallowStart() override {}
+  FailStartInstanceClient(EmbeddedWorkerTestHelper* helper)
+      : FakeEmbeddedWorkerInstanceClient(helper) {}
 
-  enum class StartMode { STALL, FAIL, SUCCEED };
-
-  void OnStartWorker(
-      int embedded_worker_id,
-      int64_t service_worker_version_id,
-      const GURL& scope,
-      const GURL& script_url,
-      bool pause_after_download,
-      blink::mojom::ServiceWorkerRequest service_worker_request,
-      blink::mojom::ControllerServiceWorkerRequest controller_request,
-      blink::mojom::EmbeddedWorkerInstanceHostAssociatedPtrInfo instance_host,
-      blink::mojom::ServiceWorkerProviderInfoForStartWorkerPtr provider_info,
-      blink::mojom::ServiceWorkerInstalledScriptsInfoPtr installed_scripts_info)
-      override {
-    switch (mode_) {
-      case StartMode::STALL:
-        // Prepare for OnStopWorker().
-        instance_host_ptr_map_[embedded_worker_id].Bind(
-            std::move(instance_host));
-        // Just keep the connection alive.
-        service_worker_request_map_[embedded_worker_id] =
-            std::move(service_worker_request);
-        controller_request_map_[embedded_worker_id] =
-            std::move(controller_request);
-        break;
-      case StartMode::FAIL:
-        ASSERT_EQ(current_mock_instance_index_ + 1,
-                  mock_instance_clients()->size());
-        // Remove the connection by peer
-        mock_instance_clients()->at(current_mock_instance_index_).reset();
-        break;
-      case StartMode::SUCCEED:
-        MessageReceiver::OnStartWorker(
-            embedded_worker_id, service_worker_version_id, scope, script_url,
-            pause_after_download, std::move(service_worker_request),
-            std::move(controller_request), std::move(instance_host),
-            std::move(provider_info), std::move(installed_scripts_info));
-        break;
-    }
-    current_mock_instance_index_++;
-  }
-
-  void OnStopWorker(int embedded_worker_id) override {
-    if (instance_host_ptr_map_[embedded_worker_id]) {
-      instance_host_ptr_map_[embedded_worker_id]->OnStopped();
-      base::RunLoop().RunUntilIdle();
-      return;
-    }
-    EmbeddedWorkerTestHelper::OnStopWorker(embedded_worker_id);
-  }
-
-  void set_start_mode(StartMode mode) { mode_ = mode; }
-
- private:
-  uint32_t current_mock_instance_index_ = 0;
-  StartMode mode_ = StartMode::STALL;
-
-  std::map<int /* embedded_worker_id */,
-           blink::mojom::
-               EmbeddedWorkerInstanceHostAssociatedPtr /* instance_host_ptr */>
-      instance_host_ptr_map_;
-  std::map<int /* embedded_worker_id */, blink::mojom::ServiceWorkerRequest>
-      service_worker_request_map_;
-  std::map<int /* embedded_worker_id */,
-           blink::mojom::ControllerServiceWorkerRequest>
-      controller_request_map_;
-  DISALLOW_COPY_AND_ASSIGN(MessageReceiverDisallowStart);
-};
-
-class ServiceWorkerFailToStartTest : public ServiceWorkerVersionTest {
- protected:
-  ServiceWorkerFailToStartTest() : ServiceWorkerVersionTest() {}
-
-  void set_start_mode(MessageReceiverDisallowStart::StartMode mode) {
-    MessageReceiverDisallowStart* helper =
-        static_cast<MessageReceiverDisallowStart*>(helper_.get());
-    helper->set_start_mode(mode);
-  }
-
-  std::unique_ptr<MessageReceiver> GetMessageReceiver() override {
-    return std::make_unique<MessageReceiverDisallowStart>();
+  void StartWorker(blink::mojom::EmbeddedWorkerStartParamsPtr params) override {
+    // Don't save the Mojo ptrs. The connection breaks.
   }
 
  private:
-  DISALLOW_COPY_AND_ASSIGN(ServiceWorkerFailToStartTest);
-};
-
-class NoOpStopWorkerEmbeddedWorkerInstanceClient
-    : public EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient {
- public:
-  explicit NoOpStopWorkerEmbeddedWorkerInstanceClient(
-      EmbeddedWorkerTestHelper* helper)
-      : EmbeddedWorkerTestHelper::MockEmbeddedWorkerInstanceClient(helper) {}
-  ~NoOpStopWorkerEmbeddedWorkerInstanceClient() override {
-  }
-
- protected:
-  void StopWorker() override {
-    // Do nothing.
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(NoOpStopWorkerEmbeddedWorkerInstanceClient);
-};
-
-class MessageReceiverDisallowStop : public MessageReceiver {
- public:
-  MessageReceiverDisallowStop() : MessageReceiver() {
-    CreateAndRegisterMockInstanceClient<
-        NoOpStopWorkerEmbeddedWorkerInstanceClient>(this);
-  }
-  ~MessageReceiverDisallowStop() override {}
-
-  void OnStopWorker(int embedded_worker_id) override {
-    // Do nothing.
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(MessageReceiverDisallowStop);
-};
-
-class ServiceWorkerStallInStoppingTest : public ServiceWorkerVersionTest {
- protected:
-  ServiceWorkerStallInStoppingTest() : ServiceWorkerVersionTest() {}
-
-  std::unique_ptr<MessageReceiver> GetMessageReceiver() override {
-    return std::make_unique<MessageReceiverDisallowStop>();
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ServiceWorkerStallInStoppingTest);
+  DISALLOW_COPY_AND_ASSIGN(FailStartInstanceClient);
 };
 
 TEST_F(ServiceWorkerVersionTest, ConcurrentStartAndStop) {
@@ -861,21 +721,20 @@
   CachedMetadataUpdateListener listener;
   version_->AddObserver(&listener);
   ASSERT_EQ(0, listener.updated_count);
+  auto* service_worker =
+      helper_->AddNewPendingServiceWorker<FakeServiceWorker>(helper_.get());
   StartWorker(version_.get(), ServiceWorkerMetrics::EventType::UNKNOWN);
+  service_worker->RunUntilInitializeGlobalScope();
 
   // Simulate requesting SetCachedMetadata from the service worker global scope.
   std::vector<uint8_t> data{1, 2, 3};
-  helper_->SimulateSetCachedMetadata(
-      version_->embedded_worker()->embedded_worker_id(), version_->script_url(),
-      data);
+  service_worker->host()->SetCachedMetadata(version_->script_url(), data);
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1, listener.updated_count);
 
   // Simulate requesting ClearCachedMetadata from the service worker global
   // scope.
-  helper_->SimulateClearCachedMetadata(
-      version_->embedded_worker()->embedded_worker_id(),
-      version_->script_url());
+  service_worker->host()->ClearCachedMetadata(version_->script_url());
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(2, listener.updated_count);
   version_->RemoveObserver(&listener);
@@ -912,76 +771,50 @@
   version_->SetAllRequestExpirations(base::TimeTicks());
 }
 
-class MessageReceiverControlEvents : public MessageReceiver {
+class DelayMessageWorker : public FakeServiceWorker {
  public:
-  MessageReceiverControlEvents() : MessageReceiver() {}
-  ~MessageReceiverControlEvents() override {}
+  explicit DelayMessageWorker(EmbeddedWorkerTestHelper* helper)
+      : FakeServiceWorker(helper) {}
+  ~DelayMessageWorker() override = default;
 
-  void OnExtendableMessageEvent(
+  void DispatchExtendableMessageEvent(
       blink::mojom::ExtendableMessageEventPtr event,
-      blink::mojom::ServiceWorker::DispatchExtendableMessageEventCallback
-          callback) override {
-    EXPECT_FALSE(extendable_message_event_callback_);
-    extendable_message_event_callback_ = std::move(callback);
+      DispatchExtendableMessageEventCallback callback) override {
+    event_ = std::move(event);
+    callback_ = std::move(callback);
+    if (quit_closure_)
+      std::move(quit_closure_).Run();
   }
 
-  void OnStopWorker(int embedded_worker_id) override {
-    EXPECT_FALSE(stop_worker_callback_);
-    stop_worker_callback_ =
-        base::BindOnce(&MessageReceiverControlEvents::SimulateWorkerStopped,
-                       base::Unretained(this), embedded_worker_id);
+  void AbortMessageEvent() {
+    std::move(callback_).Run(blink::mojom::ServiceWorkerEventStatus::ABORTED);
   }
 
-  bool has_extendable_message_event_callback() {
-    return !extendable_message_event_callback_.is_null();
-  }
-
-  blink::mojom::ServiceWorker::DispatchExtendableMessageEventCallback
-  TakeExtendableMessageEventCallback() {
-    return std::move(extendable_message_event_callback_);
-  }
-
-  base::OnceClosure stop_worker_callback() {
-    return std::move(stop_worker_callback_);
+  void RunUntilDispatchMessageEvent() {
+    if (event_)
+      return;
+    base::RunLoop loop;
+    quit_closure_ = loop.QuitClosure();
+    loop.Run();
   }
 
  private:
-  blink::mojom::ServiceWorker::DispatchExtendableMessageEventCallback
-      extendable_message_event_callback_;
-  base::OnceClosure stop_worker_callback_;
+  blink::mojom::ExtendableMessageEventPtr event_;
+  DispatchExtendableMessageEventCallback callback_;
+  base::OnceClosure quit_closure_;
+
+  DISALLOW_COPY_AND_ASSIGN(DelayMessageWorker);
 };
 
-class ServiceWorkerRequestTimeoutTest : public ServiceWorkerVersionTest {
- protected:
-  ServiceWorkerRequestTimeoutTest() : ServiceWorkerVersionTest() {}
+TEST_F(ServiceWorkerVersionTest, RequestTimeout) {
+  auto* client = helper_->AddNewPendingInstanceClient<
+      DelayedFakeEmbeddedWorkerInstanceClient>(helper_.get());
+  auto* worker =
+      helper_->AddNewPendingServiceWorker<DelayMessageWorker>(helper_.get());
 
-  std::unique_ptr<MessageReceiver> GetMessageReceiver() override {
-    return std::make_unique<MessageReceiverControlEvents>();
-  }
-
-  bool has_extendable_message_event_callback() {
-    return static_cast<MessageReceiverControlEvents*>(helper_.get())
-        ->has_extendable_message_event_callback();
-  }
-
-  blink::mojom::ServiceWorker::DispatchExtendableMessageEventCallback
-  TakeExtendableMessageEventCallback() {
-    return static_cast<MessageReceiverControlEvents*>(helper_.get())
-        ->TakeExtendableMessageEventCallback();
-  }
-
-  base::OnceClosure stop_worker_callback() {
-    return static_cast<MessageReceiverControlEvents*>(helper_.get())
-        ->stop_worker_callback();
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ServiceWorkerRequestTimeoutTest);
-};
-
-TEST_F(ServiceWorkerRequestTimeoutTest, RequestTimeout) {
   base::Optional<blink::ServiceWorkerStatusCode> error_status;
   version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
+  client->UnblockStartWorker();
   StartWorker(version_.get(),
               ServiceWorkerMetrics::EventType::FETCH_MAIN_FRAME);
 
@@ -990,31 +823,23 @@
       version_->StartRequest(ServiceWorkerMetrics::EventType::FETCH_MAIN_FRAME,
                              CreateReceiverOnCurrentThread(&error_status));
 
-  // Dispatch a dummy event whose response will be received by SWVersion.
-  EXPECT_FALSE(has_extendable_message_event_callback());
+  // Dispatch a dummy event.
   version_->endpoint()->DispatchExtendableMessageEvent(
       blink::mojom::ExtendableMessageEvent::New(),
       version_->CreateSimpleEventCallback(request_id));
+  worker->RunUntilDispatchMessageEvent();
 
-  base::RunLoop().RunUntilIdle();
-  // The renderer should have received an ExtendableMessageEvent request.
-  EXPECT_TRUE(has_extendable_message_event_callback());
-
-  // Callback has not completed yet.
+  // Request callback has not completed yet.
   EXPECT_FALSE(error_status);
-  EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version_->running_status());
 
   // Simulate timeout.
-  EXPECT_FALSE(stop_worker_callback());
   EXPECT_TRUE(version_->timeout_timer_.IsRunning());
   version_->SetAllRequestExpirations(base::TimeTicks::Now());
   version_->timeout_timer_.user_task().Run();
-  base::RunLoop().RunUntilIdle();
-
-  base::OnceClosure callback = stop_worker_callback();
 
   // The renderer should have received a StopWorker request.
-  EXPECT_TRUE(callback);
+  client->RunUntilStopWorker();
+
   // The request should have timed out.
   EXPECT_EQ(blink::ServiceWorkerStatusCode::kErrorTimeout,
             error_status.value());
@@ -1023,12 +848,11 @@
 
   // Simulate the renderer aborting the inflight event.
   // This should not crash: https://crbug.com/676984.
-  TakeExtendableMessageEventCallback().Run(
-      blink::mojom::ServiceWorkerEventStatus::ABORTED);
+  worker->AbortMessageEvent();
   base::RunLoop().RunUntilIdle();
 
   // Simulate the renderer stopping the worker.
-  std::move(callback).Run();
+  client->UnblockStopWorker();
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, version_->running_status());
 }
@@ -1188,8 +1012,10 @@
   EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, version_->running_status());
 }
 
-TEST_F(ServiceWorkerFailToStartTest, RendererCrash) {
+TEST_F(ServiceWorkerVersionTest, FailToStart_RendererCrash) {
   base::Optional<blink::ServiceWorkerStatusCode> status;
+  auto* client = helper_->AddNewPendingInstanceClient<
+      DelayedFakeEmbeddedWorkerInstanceClient>(helper_.get());
   version_->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
                         CreateReceiverOnCurrentThread(&status));
   base::RunLoop().RunUntilIdle();
@@ -1200,7 +1026,7 @@
 
   // Simulate renderer crash: break EmbeddedWorkerInstance's Mojo connection to
   // the renderer-side client.
-  helper_->mock_instance_clients()->clear();
+  client->Disconnect();
   base::RunLoop().RunUntilIdle();
 
   // Callback completed.
@@ -1209,10 +1035,13 @@
   EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, version_->running_status());
 }
 
-TEST_F(ServiceWorkerFailToStartTest, Timeout) {
+TEST_F(ServiceWorkerVersionTest, FailToStart_Timeout) {
   base::Optional<blink::ServiceWorkerStatusCode> status;
 
   // Start starting the worker.
+  auto* client = helper_->AddNewPendingInstanceClient<
+      DelayedFakeEmbeddedWorkerInstanceClient>(helper_.get());
+  client->UnblockStopWorker();
   version_->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
                         CreateReceiverOnCurrentThread(&status));
   base::RunLoop().RunUntilIdle();
@@ -1234,8 +1063,11 @@
 
 // Test that a service worker stalled in stopping will timeout and not get in a
 // sticky error state.
-TEST_F(ServiceWorkerStallInStoppingTest, DetachThenStart) {
+TEST_F(ServiceWorkerVersionTest, StallInStopping_DetachThenStart) {
   // Start a worker.
+  auto* client = helper_->AddNewPendingInstanceClient<
+      DelayedFakeEmbeddedWorkerInstanceClient>(helper_.get());
+  client->UnblockStartWorker();
   StartWorker(version_.get(), ServiceWorkerMetrics::EventType::UNKNOWN);
 
   // Try to stop the worker.
@@ -1269,8 +1101,11 @@
 
 // Test that a service worker stalled in stopping with a start worker
 // request queued up will timeout and restart.
-TEST_F(ServiceWorkerStallInStoppingTest, DetachThenRestart) {
+TEST_F(ServiceWorkerVersionTest, StallInStopping_DetachThenRestart) {
   // Start a worker.
+  auto* client = helper_->AddNewPendingInstanceClient<
+      DelayedFakeEmbeddedWorkerInstanceClient>(helper_.get());
+  client->UnblockStartWorker();
   StartWorker(version_.get(), ServiceWorkerMetrics::EventType::UNKNOWN);
 
   // Try to stop the worker.
@@ -1296,27 +1131,32 @@
 
 TEST_F(ServiceWorkerVersionTest, RendererCrashDuringEvent) {
   version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
+
+  auto* client =
+      helper_->AddNewPendingInstanceClient<FakeEmbeddedWorkerInstanceClient>(
+          helper_.get());
   StartWorker(version_.get(), ServiceWorkerMetrics::EventType::SYNC);
 
-  base::Optional<blink::ServiceWorkerStatusCode> status;
-  int request_id =
-      version_->StartRequest(ServiceWorkerMetrics::EventType::SYNC,
-                             CreateReceiverOnCurrentThread(&status));
-  base::RunLoop().RunUntilIdle();
-
-  // Callback has not completed yet.
-  EXPECT_FALSE(status);
+  base::RunLoop loop;
+  blink::ServiceWorkerStatusCode status = blink::ServiceWorkerStatusCode::kOk;
+  int request_id = version_->StartRequest(
+      ServiceWorkerMetrics::EventType::SYNC,
+      base::BindOnce(
+          [](base::OnceClosure done, blink::ServiceWorkerStatusCode* out_status,
+             blink::ServiceWorkerStatusCode result_status) {
+            *out_status = result_status;
+            std::move(done).Run();
+          },
+          loop.QuitClosure(), &status));
 
   // Simulate renderer crash: break EmbeddedWorkerInstance's Mojo connection to
-  // the renderer-side client.
-  helper_->mock_instance_clients()->clear();
-  base::RunLoop().RunUntilIdle();
-
-  // Callback completed.
-  EXPECT_EQ(blink::ServiceWorkerStatusCode::kErrorFailed, status.value());
+  // the renderer-side client. The request callback should be called.
+  client->Disconnect();
+  loop.Run();
+  EXPECT_EQ(blink::ServiceWorkerStatusCode::kErrorFailed, status);
   EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, version_->running_status());
 
-  // Request already failed, calling finsh should return false.
+  // Request already failed, calling finish should return false.
   EXPECT_FALSE(version_->FinishRequest(request_id, true /* was_handled */));
 }
 
@@ -1486,14 +1326,13 @@
       helper_->mock_render_process_host()->foreground_service_worker_count());
 }
 
-TEST_F(ServiceWorkerFailToStartTest, FailingWorkerUsesNewRendererProcess) {
+TEST_F(ServiceWorkerVersionTest, FailToStart_UseNewRendererProcess) {
   base::Optional<blink::ServiceWorkerStatusCode> status;
   ServiceWorkerContextCore* context = helper_->context();
   int64_t id = version_->version_id();
   version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
 
   // Start once. It should choose the "existing process".
-  set_start_mode(MessageReceiverDisallowStart::StartMode::SUCCEED);
   version_->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
                         CreateReceiverOnCurrentThread(&status));
   base::RunLoop().RunUntilIdle();
@@ -1505,7 +1344,8 @@
 
   // Fail once.
   status.reset();
-  set_start_mode(MessageReceiverDisallowStart::StartMode::FAIL);
+  helper_->AddPendingInstanceClient(
+      std::make_unique<FailStartInstanceClient>(helper_.get()));
   version_->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
                         CreateReceiverOnCurrentThread(&status));
   base::RunLoop().RunUntilIdle();
@@ -1515,6 +1355,8 @@
 
   // Fail again.
   status.reset();
+  helper_->AddPendingInstanceClient(
+      std::make_unique<FailStartInstanceClient>(helper_.get()));
   version_->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
                         CreateReceiverOnCurrentThread(&status));
   base::RunLoop().RunUntilIdle();
@@ -1524,7 +1366,6 @@
 
   // Succeed. It should choose the "new process".
   status.reset();
-  set_start_mode(MessageReceiverDisallowStart::StartMode::SUCCEED);
   version_->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN,
                         CreateReceiverOnCurrentThread(&status));
   base::RunLoop().RunUntilIdle();
@@ -1548,20 +1389,21 @@
   base::RunLoop().RunUntilIdle();
 }
 
-TEST_F(ServiceWorkerFailToStartTest, RestartStalledWorker) {
+TEST_F(ServiceWorkerVersionTest, FailToStart_RestartStalledWorker) {
   base::Optional<blink::ServiceWorkerStatusCode> status;
+  // Stall in starting.
+  auto* client = helper_->AddNewPendingInstanceClient<
+      DelayedFakeEmbeddedWorkerInstanceClient>(helper_.get());
+  client->UnblockStopWorker();
   version_->StartWorker(ServiceWorkerMetrics::EventType::FETCH_MAIN_FRAME,
                         CreateReceiverOnCurrentThread(&status));
   base::RunLoop().RunUntilIdle();
-  // The default start mode is StartMode::STALL. So the callback of StartWorker
-  // is not called yet.
   EXPECT_FALSE(status);
 
-  // Set StartMode::SUCCEED. So the next start worker will be successful.
-  set_start_mode(MessageReceiverDisallowStart::StartMode::SUCCEED);
-
-  // StartWorker message will be sent again because OnStopped is called before
-  // OnStarted.
+  // The restart logic is triggered because OnStopped is called before
+  // OnStarted. So the Start message is sent again. The delayed instance client
+  // was already consumed, so a default fake instance client will be created,
+  // which starts normally.
   bool has_stopped = false;
   version_->StopWorker(base::BindOnce(&VerifyCalled, &has_stopped));
   base::RunLoop().RunUntilIdle();
diff --git a/content/browser/web_package/signed_exchange_envelope.cc b/content/browser/web_package/signed_exchange_envelope.cc
index e503346..e74286b 100644
--- a/content/browser/web_package/signed_exchange_envelope.cc
+++ b/content/browser/web_package/signed_exchange_envelope.cc
@@ -262,6 +262,13 @@
     return false;
   }
 
+  found = out->response_headers().find("digest");
+  if (found == out->response_headers().end()) {
+    signed_exchange_utils::ReportErrorAndTraceEvent(
+        devtools_proxy, "Signed exchange has no Digest: header");
+    return false;
+  }
+
   return true;
 }
 
diff --git a/content/browser/web_package/signed_exchange_envelope_unittest.cc b/content/browser/web_package/signed_exchange_envelope_unittest.cc
index 49a0e3f..5065803 100644
--- a/content/browser/web_package/signed_exchange_envelope_unittest.cc
+++ b/content/browser/web_package/signed_exchange_envelope_unittest.cc
@@ -111,11 +111,11 @@
 TEST_P(SignedExchangeEnvelopeTest, ValidHeader) {
   auto header = GenerateHeaderAndParse(
       GetParam(), "https://test.example.org/test/", kSignatureString,
-      {{kStatusKey, "200"}, {"content-type", "text/html"}});
+      {{kStatusKey, "200"}, {"content-type", "text/html"}, {"digest", "foo"}});
   ASSERT_TRUE(header.has_value());
   EXPECT_EQ(header->request_url().url, GURL("https://test.example.org/test/"));
   EXPECT_EQ(header->response_code(), static_cast<net::HttpStatusCode>(200u));
-  EXPECT_EQ(header->response_headers().size(), 1u);
+  EXPECT_EQ(header->response_headers().size(), 2u);
 }
 
 TEST_P(SignedExchangeEnvelopeTest, InformationalResponseCode) {
@@ -226,6 +226,7 @@
       {
           {kStatusKey, "200"},
           {"cache-control", "foo=\"300, no-store\""},
+          {"digest", "foo"},
       });
   ASSERT_TRUE(header.has_value());
 }
diff --git a/content/browser/web_package/signed_exchange_handler.cc b/content/browser/web_package/signed_exchange_handler.cc
index a49cde4..68172c7 100644
--- a/content/browser/web_package/signed_exchange_handler.cc
+++ b/content/browser/web_package/signed_exchange_handler.cc
@@ -652,16 +652,8 @@
   response_head.load_timing.receive_headers_end = now;
 
   std::string digest_header_value;
-  if (!response_head.headers->EnumerateHeader(nullptr, kDigestHeader,
-                                              &digest_header_value)) {
-    // TODO(https://crbug.com/803774): Detect this error in
-    // SignedExchangeEnvelope::Parse().
-    signed_exchange_utils::ReportErrorAndTraceEvent(
-        devtools_proxy_.get(), "Signed exchange has no Digest: header");
-    RunErrorCallback(SignedExchangeLoadResult::kHeaderParseError,
-                     net::ERR_INVALID_SIGNED_EXCHANGE);
-    return;
-  }
+  response_head.headers->EnumerateHeader(nullptr, kDigestHeader,
+                                         &digest_header_value);
   auto mi_stream = std::make_unique<MerkleIntegritySourceStream>(
       digest_header_value, std::move(source_));
 
diff --git a/content/browser/worker_host/worker_script_fetch_initiator.cc b/content/browser/worker_host/worker_script_fetch_initiator.cc
index a8f396c..a08976d 100644
--- a/content/browser/worker_host/worker_script_fetch_initiator.cc
+++ b/content/browser/worker_host/worker_script_fetch_initiator.cc
@@ -23,11 +23,13 @@
 #include "content/browser/worker_host/worker_script_loader_factory.h"
 #include "content/common/content_constants_internal.h"
 #include "content/common/navigation_subresource_loader_params.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
+#include "content/public/browser/resource_context.h"
 #include "content/public/browser/shared_cors_origin_access_list.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
@@ -62,6 +64,14 @@
          resource_type == RESOURCE_TYPE_SHARED_WORKER)
       << resource_type;
 
+  BrowserContext* browser_context = storage_partition->browser_context();
+  ResourceContext* resource_context =
+      browser_context ? browser_context->GetResourceContext() : nullptr;
+  if (!browser_context || !resource_context) {
+    // The browser is shutting down. Just drop this request.
+    return;
+  }
+
   bool constructor_uses_file_url =
       request_initiator.scheme() == url::kFileScheme;
 
@@ -88,12 +98,18 @@
     resource_request->request_initiator = request_initiator;
     resource_request->resource_type = resource_type;
 
-    AddAdditionalRequestHeaders(resource_request.get(),
-                                storage_partition->browser_context());
+    AddAdditionalRequestHeaders(resource_request.get(), browser_context);
   }
 
   // Bounce to the IO thread to setup service worker and appcache support in
   // case the request for the worker script will need to be intercepted by them.
+  //
+  // This passes |resource_context| to the IO thread. |resource_context| will
+  // not be destroyed before the task runs, because the shutdown sequence is:
+  // 1. (UI thread) StoragePartitionImpl destructs.
+  // 2. (IO thread) ResourceContext destructs.
+  // Since |storage_partition| is alive, we must be before step 1, so this
+  // task we post to the IO thread must run before step 2.
   base::PostTaskWithTraits(
       FROM_HERE, {BrowserThread::IO},
       base::BindOnce(
@@ -101,7 +117,7 @@
           std::move(resource_request),
           storage_partition->url_loader_factory_getter(),
           std::move(factory_bundle_for_browser),
-          std::move(subresource_loader_factories),
+          std::move(subresource_loader_factories), resource_context,
           std::move(service_worker_context), appcache_handle_core,
           blob_url_loader_factory ? blob_url_loader_factory->Clone() : nullptr,
           std::move(callback)));
@@ -231,18 +247,21 @@
         factory_bundle_for_browser_info,
     std::unique_ptr<blink::URLLoaderFactoryBundleInfo>
         subresource_loader_factories,
-    scoped_refptr<ServiceWorkerContextWrapper> context,
+    ResourceContext* resource_context,
+    scoped_refptr<ServiceWorkerContextWrapper> service_worker_context,
     AppCacheNavigationHandleCore* appcache_handle_core,
     std::unique_ptr<network::SharedURLLoaderFactoryInfo>
         blob_url_loader_factory_info,
     CompletionCallback callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   DCHECK(blink::ServiceWorkerUtils::IsServicificationEnabled());
+  DCHECK(resource_context);
 
   // Set up for service worker.
   auto provider_info = blink::mojom::ServiceWorkerProviderInfoForWorker::New();
-  base::WeakPtr<ServiceWorkerProviderHost> host =
-      context->PreCreateHostForSharedWorker(process_id, &provider_info);
+  base::WeakPtr<ServiceWorkerProviderHost> service_worker_host =
+      service_worker_context->PreCreateHostForSharedWorker(process_id,
+                                                           &provider_info);
 
   // Create the URL loader factory for WorkerScriptLoaderFactory to use to load
   // the main script.
@@ -289,13 +308,14 @@
         base::BindRepeating([]() -> WebContents* { return nullptr; });
     std::vector<std::unique_ptr<URLLoaderThrottle>> throttles =
         GetContentClient()->browser()->CreateURLLoaderThrottles(
-            *resource_request, context->resource_context(), wc_getter,
+            *resource_request, resource_context, wc_getter,
             nullptr /* navigation_ui_data */, -1 /* frame_tree_node_id */);
 
     WorkerScriptFetcher::CreateAndStart(
         std::make_unique<WorkerScriptLoaderFactory>(
-            process_id, host, std::move(appcache_host),
-            context->resource_context(), std::move(url_loader_factory)),
+            process_id, std::move(service_worker_host),
+            std::move(appcache_host), resource_context,
+            std::move(url_loader_factory)),
         std::move(throttles), std::move(resource_request),
         base::BindOnce(WorkerScriptFetchInitiator::DidCreateScriptLoaderOnIO,
                        std::move(callback), std::move(provider_info),
@@ -308,8 +328,8 @@
   network::mojom::URLLoaderFactoryAssociatedPtrInfo main_script_loader_factory;
   mojo::MakeStrongAssociatedBinding(
       std::make_unique<WorkerScriptLoaderFactory>(
-          process_id, host->AsWeakPtr(), std::move(appcache_host),
-          context->resource_context(), std::move(url_loader_factory)),
+          process_id, std::move(service_worker_host), std::move(appcache_host),
+          resource_context, std::move(url_loader_factory)),
       mojo::MakeRequest(&main_script_loader_factory));
 
   DidCreateScriptLoaderOnIO(std::move(callback), std::move(provider_info),
diff --git a/content/browser/worker_host/worker_script_fetch_initiator.h b/content/browser/worker_host/worker_script_fetch_initiator.h
index 8bf63d9..1d78977 100644
--- a/content/browser/worker_host/worker_script_fetch_initiator.h
+++ b/content/browser/worker_host/worker_script_fetch_initiator.h
@@ -31,6 +31,7 @@
 
 class AppCacheNavigationHandleCore;
 class BrowserContext;
+class ResourceContext;
 class ServiceWorkerContextWrapper;
 class StoragePartitionImpl;
 class URLLoaderFactoryGetter;
@@ -83,7 +84,8 @@
           factory_bundle_for_browser_info,
       std::unique_ptr<blink::URLLoaderFactoryBundleInfo>
           subresource_loader_factories,
-      scoped_refptr<ServiceWorkerContextWrapper> context,
+      ResourceContext* resource_context,
+      scoped_refptr<ServiceWorkerContextWrapper> service_worker_context,
       AppCacheNavigationHandleCore* appcache_handle_core,
       std::unique_ptr<network::SharedURLLoaderFactoryInfo>
           blob_url_loader_factory_info,
diff --git a/content/browser/worker_host/worker_script_loader.cc b/content/browser/worker_host/worker_script_loader.cc
index a5a14ff..2423559b 100644
--- a/content/browser/worker_host/worker_script_loader.cc
+++ b/content/browser/worker_host/worker_script_loader.cc
@@ -239,7 +239,7 @@
 
 void WorkerScriptLoader::OnComplete(
     const network::URLLoaderCompletionStatus& status) {
-  if (status.error_code == net::OK)
+  if (service_worker_provider_host_ && status.error_code == net::OK)
     service_worker_provider_host_->CompleteSharedWorkerPreparation();
   client_->OnComplete(status);
 }
diff --git a/content/browser/worker_host/worker_script_loader.h b/content/browser/worker_host/worker_script_loader.h
index affc0c5..0cf0badb 100644
--- a/content/browser/worker_host/worker_script_loader.h
+++ b/content/browser/worker_host/worker_script_loader.h
@@ -123,7 +123,7 @@
   network::ResourceRequest resource_request_;
   network::mojom::URLLoaderClientPtr client_;
   base::WeakPtr<ServiceWorkerProviderHost> service_worker_provider_host_;
-  ResourceContext* resource_context_;
+  ResourceContext* const resource_context_;
   scoped_refptr<network::SharedURLLoaderFactory> default_loader_factory_;
   net::MutableNetworkTrafficAnnotationTag traffic_annotation_;
 
diff --git a/content/browser/worker_host/worker_script_loader_factory.cc b/content/browser/worker_host/worker_script_loader_factory.cc
index c64846a..908379e8 100644
--- a/content/browser/worker_host/worker_script_loader_factory.cc
+++ b/content/browser/worker_host/worker_script_loader_factory.cc
@@ -31,14 +31,18 @@
       appcache_host_(std::move(appcache_host)),
       resource_context_(resource_context),
       loader_factory_(std::move(loader_factory)) {
+#if DCHECK_IS_ON()
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   DCHECK(blink::ServiceWorkerUtils::IsServicificationEnabled());
   // In the current implementation, dedicated workers use
   // ServiceWorkerProviderHost w/ kForSharedWorker.
   // TODO(nhiroki): Rename it to kForWorker for both dedicated workers and
   // shared workers, or add kForDedicatedWorker (https://crbug.com/906991).
-  DCHECK_EQ(service_worker_provider_host_->provider_type(),
-            blink::mojom::ServiceWorkerProviderType::kForSharedWorker);
+  if (service_worker_provider_host_) {
+    DCHECK_EQ(service_worker_provider_host_->provider_type(),
+              blink::mojom::ServiceWorkerProviderType::kForSharedWorker);
+  }
+#endif  // DCHECK_IS_ON()
 }
 
 WorkerScriptLoaderFactory::~WorkerScriptLoaderFactory() {
@@ -78,6 +82,12 @@
       << resource_request.resource_type;
   DCHECK(!script_loader_);
 
+  // TODO(falken): There's no guarantee |resource_context_| is still valid here.
+  // WorkerScriptLoaderFactory needs access to an IO thread class that
+  // can tell it if shutdown has started (e.g.,
+  // ServiceWorkerContextWrapper::resource_context(), though it's weird to
+  // depend on service worker infra for this.
+
   // Create a WorkerScriptLoader to load the script.
   auto script_loader = std::make_unique<WorkerScriptLoader>(
       process_id_, routing_id, request_id, options, resource_request,
diff --git a/content/browser/worker_host/worker_script_loader_factory.h b/content/browser/worker_host/worker_script_loader_factory.h
index 6500e3de..3344080 100644
--- a/content/browser/worker_host/worker_script_loader_factory.h
+++ b/content/browser/worker_host/worker_script_loader_factory.h
@@ -29,7 +29,8 @@
 //
 // This is created per one web worker. All functions of this class must be
 // called on the IO thread.
-class WorkerScriptLoaderFactory : public network::mojom::URLLoaderFactory {
+class CONTENT_EXPORT WorkerScriptLoaderFactory
+    : public network::mojom::URLLoaderFactory {
  public:
   // |loader_factory| is used to load the script if the load is not intercepted
   // by a feature like service worker. Typically it will load the script from
@@ -37,7 +38,7 @@
   // factories used for non-http(s) URLs, e.g., a chrome-extension:// URL.
   WorkerScriptLoaderFactory(
       int process_id,
-      base::WeakPtr<ServiceWorkerProviderHost> provider_host,
+      base::WeakPtr<ServiceWorkerProviderHost> service_worker_provider_host,
       base::WeakPtr<AppCacheHost> appcache_host,
       ResourceContext* resource_context,
       scoped_refptr<network::SharedURLLoaderFactory> loader_factory);
diff --git a/content/browser/worker_host/worker_script_loader_factory_unittest.cc b/content/browser/worker_host/worker_script_loader_factory_unittest.cc
new file mode 100644
index 0000000..26c307a
--- /dev/null
+++ b/content/browser/worker_host/worker_script_loader_factory_unittest.cc
@@ -0,0 +1,180 @@
+// Copyright 2019 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/browser/worker_host/worker_script_loader_factory.h"
+
+#include "base/bind_helpers.h"
+#include "base/run_loop.h"
+#include "content/browser/service_worker/embedded_worker_test_helper.h"
+#include "content/browser/service_worker/service_worker_context_core.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "mojo/public/cpp/bindings/binding_set.h"
+#include "net/http/http_util.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/wrapper_shared_url_loader_factory.h"
+#include "services/network/test/test_url_loader_client.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+namespace {
+
+// A URLLoaderFactory that returns 200 OK with an empty javascript to any
+// request.
+// TODO(bashi): Avoid duplicated MockNetworkURLLoaderFactory. This is almost the
+// same as EmbeddedWorkerTestHelper::MockNetworkURLLoaderFactory.
+class MockNetworkURLLoaderFactory final
+    : public network::mojom::URLLoaderFactory {
+ public:
+  MockNetworkURLLoaderFactory() = default;
+
+  // network::mojom::URLLoaderFactory implementation.
+  void CreateLoaderAndStart(network::mojom::URLLoaderRequest request,
+                            int32_t routing_id,
+                            int32_t request_id,
+                            uint32_t options,
+                            const network::ResourceRequest& url_request,
+                            network::mojom::URLLoaderClientPtr client,
+                            const net::MutableNetworkTrafficAnnotationTag&
+                                traffic_annotation) override {
+    const std::string headers =
+        "HTTP/1.1 200 OK\n"
+        "Content-Type: application/javascript\n\n";
+    net::HttpResponseInfo info;
+    info.headers = new net::HttpResponseHeaders(
+        net::HttpUtil::AssembleRawHeaders(headers.c_str(), headers.length()));
+    network::ResourceResponseHead response;
+    response.headers = info.headers;
+    response.headers->GetMimeType(&response.mime_type);
+    client->OnReceiveResponse(response);
+
+    const std::string body = "/*this body came from the network*/";
+    uint32_t bytes_written = body.size();
+    mojo::DataPipe data_pipe;
+    data_pipe.producer_handle->WriteData(body.data(), &bytes_written,
+                                         MOJO_WRITE_DATA_FLAG_ALL_OR_NONE);
+    client->OnStartLoadingResponseBody(std::move(data_pipe.consumer_handle));
+
+    network::URLLoaderCompletionStatus status;
+    status.error_code = net::OK;
+    client->OnComplete(status);
+  }
+
+  void Clone(network::mojom::URLLoaderFactoryRequest request) override {
+    bindings_.AddBinding(this, std::move(request));
+  }
+
+ private:
+  mojo::BindingSet<network::mojom::URLLoaderFactory> bindings_;
+  DISALLOW_COPY_AND_ASSIGN(MockNetworkURLLoaderFactory);
+};
+
+}  // namespace
+
+class WorkerScriptLoaderFactoryTest : public testing::Test {
+ public:
+  WorkerScriptLoaderFactoryTest()
+      : browser_thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {}
+  ~WorkerScriptLoaderFactoryTest() override = default;
+
+  void SetUp() override {
+    helper_ = std::make_unique<EmbeddedWorkerTestHelper>(base::FilePath());
+    ServiceWorkerContextCore* context = helper_->context();
+    context->storage()->LazyInitializeForTest(base::DoNothing());
+    base::RunLoop().RunUntilIdle();
+
+    network_loader_factory_instance_ =
+        std::make_unique<MockNetworkURLLoaderFactory>();
+    network::mojom::URLLoaderFactoryPtrInfo factory;
+    network_loader_factory_instance_->Clone(mojo::MakeRequest(&factory));
+    auto info = std::make_unique<network::WrapperSharedURLLoaderFactoryInfo>(
+        std::move(factory));
+    network_loader_factory_ =
+        network::SharedURLLoaderFactory::Create(std::move(info));
+  }
+
+ protected:
+  network::mojom::URLLoaderPtr CreateTestLoaderAndStart(
+      const GURL& url,
+      WorkerScriptLoaderFactory* factory,
+      network::TestURLLoaderClient* client) {
+    network::mojom::URLLoaderPtr loader;
+    network::ResourceRequest resource_request;
+    resource_request.url = url;
+    resource_request.resource_type = RESOURCE_TYPE_SHARED_WORKER;
+    factory->CreateLoaderAndStart(
+        mojo::MakeRequest(&loader), 0 /* routing_id */, 0 /* request_id */,
+        network::mojom::kURLLoadOptionNone, resource_request,
+        client->CreateInterfacePtr(),
+        net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
+    return loader;
+  }
+
+  TestBrowserThreadBundle browser_thread_bundle_;
+  std::unique_ptr<EmbeddedWorkerTestHelper> helper_;
+  std::unique_ptr<MockNetworkURLLoaderFactory> network_loader_factory_instance_;
+  scoped_refptr<network::SharedURLLoaderFactory> network_loader_factory_;
+};
+
+TEST_F(WorkerScriptLoaderFactoryTest, ServiceWorkerProviderHost) {
+  // Make a service worker provider host for the shared worker.
+  auto service_worker_provider_info =
+      blink::mojom::ServiceWorkerProviderInfoForWorker::New();
+  base::WeakPtr<ServiceWorkerProviderHost> service_worker_provider_host =
+      ServiceWorkerProviderHost::PreCreateForSharedWorker(
+          helper_->context()->AsWeakPtr(), 1 /* process_id */,
+          &service_worker_provider_info);
+
+  // Skip AppCache host as it's not worth testing.
+  base::WeakPtr<AppCacheHost> appcache_host;
+
+  // Make the factory.
+  std::unique_ptr<WorkerScriptLoaderFactory> factory =
+      std::make_unique<WorkerScriptLoaderFactory>(
+          1 /* process_id */, service_worker_provider_host, appcache_host,
+          nullptr /* resource_context */, network_loader_factory_);
+
+  // Load the script.
+  GURL url("https://www.example.com/worker.js");
+  network::TestURLLoaderClient client;
+  network::mojom::URLLoaderPtr loader =
+      CreateTestLoaderAndStart(url, factory.get(), &client);
+  client.RunUntilComplete();
+  EXPECT_EQ(net::OK, client.completion_status().error_code);
+
+  // The provider host should be set up.
+  EXPECT_TRUE(service_worker_provider_host->is_response_committed());
+  EXPECT_TRUE(service_worker_provider_host->is_execution_ready());
+  EXPECT_EQ(url, service_worker_provider_host->url());
+}
+
+// Test a null service worker provider host. This typically only happens during
+// shutdown or after a fatal error occurred in the service worker system.
+TEST_F(WorkerScriptLoaderFactoryTest, NullServiceWorkerProviderHost) {
+  // Use a null service worker provider host.
+  base::WeakPtr<ServiceWorkerProviderHost> service_worker_provider_host;
+
+  // Skip AppCache host as it's not worth testing.
+  base::WeakPtr<AppCacheHost> appcache_host;
+
+  // Make the factory.
+  std::unique_ptr<WorkerScriptLoaderFactory> factory =
+      std::make_unique<WorkerScriptLoaderFactory>(
+          1 /* process_id */, service_worker_provider_host, appcache_host,
+          nullptr /* resource_context */, network_loader_factory_);
+
+  // Load the script.
+  GURL url("https://www.example.com/worker.js");
+  network::TestURLLoaderClient client;
+  network::mojom::URLLoaderPtr loader =
+      CreateTestLoaderAndStart(url, factory.get(), &client);
+  client.RunUntilComplete();
+  EXPECT_EQ(net::OK, client.completion_status().error_code);
+}
+
+// TODO(falken): Add a test for a shared worker that's controlled by a service
+// worker.
+
+}  // namespace content
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index 8b61441..9d9590e 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -484,6 +484,9 @@
   WebRuntimeFeatures::EnableMimeHandlerViewInCrossProcessFrame(
       base::FeatureList::IsEnabled(
           features::kMimeHandlerViewInCrossProcessFrame));
+
+  if (base::FeatureList::IsEnabled(features::kUserAgentClientHint))
+    WebRuntimeFeatures::EnableFeatureFromString("UserAgentClientHint", true);
 }
 
 }  // namespace
diff --git a/content/common/renderer.mojom b/content/common/renderer.mojom
index a9f2e5c..227e197 100644
--- a/content/common/renderer.mojom
+++ b/content/common/renderer.mojom
@@ -15,6 +15,7 @@
 import "third_party/blink/public/mojom/service_worker/embedded_worker.mojom";
 import "third_party/blink/public/mojom/user_agent/user_agent_metadata.mojom";
 import "ui/gfx/geometry/mojo/geometry.mojom";
+import "url/mojom/url.mojom";
 
 struct CreateViewParams {
   // Renderer-wide preferences.
@@ -256,7 +257,9 @@
   // Tells the renderer process that it has been locked to a site (i.e., a
   // scheme plus eTLD+1, such as https://google.com), or to a more specific
   // origin.
-  SetIsLockedToSite();
+  // TODO(nasko): Remove |lock_url| after we've gathered enough information to
+  // debug issues with browser-side security checks. https://crbug.com/931895.
+  SetIsLockedToSite(url.mojom.Url lock_url);
 
   // Tells the renderer to enable V8's memory saving mode when possible.
   // This is only used when site-per-process is enabled. If the process
diff --git a/content/public/android/BUILD.gn b/content/public/android/BUILD.gn
index e368e78..628baf4 100644
--- a/content/public/android/BUILD.gn
+++ b/content/public/android/BUILD.gn
@@ -150,6 +150,7 @@
     "java/src/org/chromium/content/browser/SpeechRecognitionImpl.java",
     "java/src/org/chromium/content/browser/SyntheticGestureTarget.java",
     "java/src/org/chromium/content/browser/TracingControllerAndroidImpl.java",
+    "java/src/org/chromium/content/browser/UiConstants.java",
     "java/src/org/chromium/content/browser/UiThreadTaskTraitsImpl.java",
     "java/src/org/chromium/content/browser/ViewEventSinkImpl.java",
     "java/src/org/chromium/content/browser/WindowEventObserver.java",
@@ -401,6 +402,7 @@
     "java/src/org/chromium/content/browser/SyntheticGestureTarget.java",
     "java/src/org/chromium/content/browser/TracingControllerAndroidImpl.java",
     "java/src/org/chromium/content/browser/TtsPlatformImpl.java",
+    "java/src/org/chromium/content/browser/UiConstants.java",
     "java/src/org/chromium/content/browser/accessibility/BrowserAccessibilityState.java",
     "java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java",
     "java/src/org/chromium/content/browser/accessibility/captioning/CaptioningController.java",
diff --git a/content/public/android/java/src/org/chromium/content/browser/UiConstants.java b/content/public/android/java/src/org/chromium/content/browser/UiConstants.java
new file mode 100644
index 0000000..5234dcc
--- /dev/null
+++ b/content/public/android/java/src/org/chromium/content/browser/UiConstants.java
@@ -0,0 +1,81 @@
+// Copyright 2019 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.
+
+package org.chromium.content.browser;
+
+import org.chromium.base.Log;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.annotations.CalledByNative;
+
+/**
+ * Platform-provided UI constants.
+ */
+public class UiConstants {
+    private static final String TAG = "UiConstants";
+    private static final String UI_CONSTANTS_INTERNAL =
+            "org.chromium.content.browser.UiConstantsInternal";
+    private static UiConstants sInstance;
+
+    private static UiConstants getInstance() {
+        ThreadUtils.assertOnUiThread();
+        if (sInstance != null) return sInstance;
+        try {
+            sInstance = (UiConstants) Class.forName(UI_CONSTANTS_INTERNAL).newInstance();
+        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
+                | IllegalArgumentException e) {
+            Log.w(TAG, "Could not summon UiConstantsInternal", e);
+            sInstance = new UiConstants();
+        }
+        return sInstance;
+    }
+
+    @CalledByNative
+    private static boolean isFocusRingOutset() {
+        return getInstance().isFocusRingOutsetInternal();
+    }
+
+    @CalledByNative
+    private static boolean hasCustomFocusRingColor() {
+        return getInstance().hasCustomFocusRingColorInternal();
+    }
+
+    @CalledByNative
+    private static int getFocusRingColor() {
+        return getInstance().getFocusRingColorInternal();
+    }
+
+    @CalledByNative
+    private static boolean hasCustomMinimumStrokeWidthForFocusRing() {
+        return getInstance().hasCustomMinimumStrokeWidthForFocusRingInternal();
+    }
+
+    @CalledByNative
+    private static float getMinimumStrokeWidthForFocusRing() {
+        return getInstance().getMinimumStrokeWidthForFocusRingInternal();
+    }
+
+    protected UiConstants() {}
+
+    protected boolean isFocusRingOutsetInternal() {
+        return false;
+    }
+
+    protected boolean hasCustomFocusRingColorInternal() {
+        return false;
+    }
+
+    protected int getFocusRingColorInternal() {
+        assert false;
+        return 0;
+    }
+
+    protected boolean hasCustomMinimumStrokeWidthForFocusRingInternal() {
+        return false;
+    }
+
+    protected float getMinimumStrokeWidthForFocusRingInternal() {
+        assert false;
+        return 1.0f;
+    }
+}
diff --git a/content/public/browser/BUILD.gn b/content/public/browser/BUILD.gn
index 61b18ad..b1aa57b 100644
--- a/content/public/browser/BUILD.gn
+++ b/content/public/browser/BUILD.gn
@@ -249,6 +249,8 @@
     "render_widget_host_iterator.h",
     "render_widget_host_view.h",
     "render_widget_host_view_mac_delegate.h",
+    "renderer_preferences_util.cc",
+    "renderer_preferences_util.h",
     "replaced_navigation_entry_data.h",
     "resource_context.h",
     "resource_dispatcher_host.h",
diff --git a/content/public/browser/overlay_window.h b/content/public/browser/overlay_window.h
index 64eadcaf..3ae666d 100644
--- a/content/public/browser/overlay_window.h
+++ b/content/public/browser/overlay_window.h
@@ -61,6 +61,7 @@
   virtual void SetAlwaysHidePlayPauseButton(bool is_visible) = 0;
   virtual void SetSkipAdButtonVisibility(bool is_visible) = 0;
   virtual void SetNextTrackButtonVisibility(bool is_visible) = 0;
+  virtual void SetPreviousTrackButtonVisibility(bool is_visible) = 0;
 
   // Retrieves the ui::Layers corresponding to the window and video.
   virtual ui::Layer* GetWindowBackgroundLayer() = 0;
diff --git a/content/public/browser/picture_in_picture_window_controller.h b/content/public/browser/picture_in_picture_window_controller.h
index bd1595e5..ec7807fb 100644
--- a/content/public/browser/picture_in_picture_window_controller.h
+++ b/content/public/browser/picture_in_picture_window_controller.h
@@ -73,6 +73,9 @@
   // Called when the user interacts with the "Next Track" control.
   virtual void NextTrack() = 0;
 
+  // Called when the user interacts with the "Previous Track" control.
+  virtual void PreviousTrack() = 0;
+
   // Commands.
   // Returns true if the player is active (i.e. currently playing) after this
   // call.
diff --git a/content/public/browser/renderer_preferences_util.cc b/content/public/browser/renderer_preferences_util.cc
new file mode 100644
index 0000000..b7de4425
--- /dev/null
+++ b/content/public/browser/renderer_preferences_util.cc
@@ -0,0 +1,51 @@
+// Copyright 2019 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/renderer_preferences_util.h"
+
+#include "base/command_line.h"
+#include "base/no_destructor.h"
+#include "base/strings/string_number_conversions.h"
+#include "build/build_config.h"
+#include "content/public/common/content_switches.h"
+#include "third_party/blink/public/mojom/renderer_preferences.mojom.h"
+#include "ui/gfx/font_render_params.h"
+
+#if defined(OS_ANDROID)
+#include "content/browser/android/android_ui_constants.h"
+#endif
+
+namespace content {
+
+void UpdateFontRendererPreferencesFromSystemSettings(
+    blink::mojom::RendererPreferences* prefs) {
+  static const base::NoDestructor<gfx::FontRenderParams> params(
+      gfx::GetFontRenderParams(gfx::FontRenderParamsQuery(), nullptr));
+  prefs->should_antialias_text = params->antialiasing;
+  prefs->use_subpixel_positioning = params->subpixel_positioning;
+  prefs->hinting = params->hinting;
+  prefs->use_autohinter = params->autohinter;
+  prefs->use_bitmaps = params->use_bitmaps;
+  prefs->subpixel_rendering = params->subpixel_rendering;
+}
+
+void UpdateFocusRingPreferencesFromSystemSettings(
+    blink::mojom::RendererPreferences* prefs) {
+#if defined(OS_ANDROID)
+  prefs->is_focus_ring_outset = AndroidUiConstants::IsFocusRingOutset();
+
+  base::Optional<float> stroke_width =
+      AndroidUiConstants::GetMinimumStrokeWidthForFocusRing();
+  if (stroke_width)
+    prefs->minimum_stroke_width_for_focus_ring = *stroke_width;
+
+  base::Optional<SkColor> color = AndroidUiConstants::GetFocusRingColor();
+  if (color) {
+    prefs->use_custom_colors = true;
+    prefs->focus_ring_color = *color;
+  }
+#endif
+}
+
+}  // namespace content
diff --git a/content/public/common/renderer_preferences_util.h b/content/public/browser/renderer_preferences_util.h
similarity index 62%
rename from content/public/common/renderer_preferences_util.h
rename to content/public/browser/renderer_preferences_util.h
index e1fbe6d9..bfd5be8 100644
--- a/content/public/common/renderer_preferences_util.h
+++ b/content/public/browser/renderer_preferences_util.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CONTENT_PUBLIC_COMMON_RENDERER_PREFERENCES_UTIL_H_
-#define CONTENT_PUBLIC_COMMON_RENDERER_PREFERENCES_UTIL_H_
+#ifndef CONTENT_PUBLIC_BROWSER_RENDERER_PREFERENCES_UTIL_H_
+#define CONTENT_PUBLIC_BROWSER_RENDERER_PREFERENCES_UTIL_H_
 
 #include "content/common/content_export.h"
 
@@ -19,6 +19,9 @@
 CONTENT_EXPORT void UpdateFontRendererPreferencesFromSystemSettings(
     blink::mojom::RendererPreferences* prefs);
 
+CONTENT_EXPORT void UpdateFocusRingPreferencesFromSystemSettings(
+    blink::mojom::RendererPreferences* prefs);
+
 }  // namespace content
 
-#endif  // CONTENT_PUBLIC_COMMON_RENDERER_PREFERENCES_UTIL_H_
+#endif  // CONTENT_PUBLIC_BROWSER_RENDERER_PREFERENCES_UTIL_H_
diff --git a/content/public/common/BUILD.gn b/content/public/common/BUILD.gn
index 1e89170e..22adef2 100644
--- a/content/public/common/BUILD.gn
+++ b/content/public/common/BUILD.gn
@@ -184,8 +184,6 @@
     "referrer.h",
     "referrer_type_converters.cc",
     "referrer_type_converters.h",
-    "renderer_preferences_util.cc",
-    "renderer_preferences_util.h",
     "resource_intercept_policy.h",
     "resource_request_body_android.cc",
     "resource_request_body_android.h",
diff --git a/content/public/common/common_param_traits_macros.h b/content/public/common/common_param_traits_macros.h
index 37693ee..8db2b82 100644
--- a/content/public/common/common_param_traits_macros.h
+++ b/content/public/common/common_param_traits_macros.h
@@ -305,6 +305,10 @@
   IPC_STRUCT_TRAITS_MEMBER(subpixel_rendering)
   IPC_STRUCT_TRAITS_MEMBER(use_subpixel_positioning)
   IPC_STRUCT_TRAITS_MEMBER(focus_ring_color)
+#if defined(OS_ANDROID)
+  IPC_STRUCT_TRAITS_MEMBER(minimum_stroke_width_for_focus_ring)
+  IPC_STRUCT_TRAITS_MEMBER(is_focus_ring_outset)
+#endif
   IPC_STRUCT_TRAITS_MEMBER(active_selection_bg_color)
   IPC_STRUCT_TRAITS_MEMBER(active_selection_fg_color)
   IPC_STRUCT_TRAITS_MEMBER(inactive_selection_bg_color)
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 8f69d78..cb2213e 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -498,6 +498,11 @@
 const base::Feature kUserActivationV2{"UserActivationV2",
                                       base::FEATURE_ENABLED_BY_DEFAULT};
 
+// An experimental replacement for the `User-Agent` header, defined in
+// https://tools.ietf.org/html/draft-west-ua-client-hints.
+const base::Feature kUserAgentClientHint{"UserAgentClientHint",
+                                         base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables V8's low memory mode for subframes. This is used only
 // in conjunction with the --site-per-process feature.
 const base::Feature kV8LowMemoryModeForSubframes{
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index b57a897..9a957af 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -120,6 +120,7 @@
 CONTENT_EXPORT extern const base::Feature kTouchpadOverscrollHistoryNavigation;
 CONTENT_EXPORT extern const base::Feature kUserActivationSameOriginVisibility;
 CONTENT_EXPORT extern const base::Feature kUserActivationV2;
+CONTENT_EXPORT extern const base::Feature kUserAgentClientHint;
 CONTENT_EXPORT extern const base::Feature kV8LowMemoryModeForSubframes;
 CONTENT_EXPORT extern const base::Feature kV8Orinoco;
 CONTENT_EXPORT extern const base::Feature kV8VmFuture;
diff --git a/content/public/common/renderer_preferences_util.cc b/content/public/common/renderer_preferences_util.cc
deleted file mode 100644
index d4e4226..0000000
--- a/content/public/common/renderer_preferences_util.cc
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2019 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/common/renderer_preferences_util.h"
-
-#include "base/no_destructor.h"
-#include "third_party/blink/public/mojom/renderer_preferences.mojom.h"
-#include "ui/gfx/font_render_params.h"
-
-namespace content {
-
-void UpdateFontRendererPreferencesFromSystemSettings(
-    blink::mojom::RendererPreferences* prefs) {
-  static const base::NoDestructor<gfx::FontRenderParams> params(
-      gfx::GetFontRenderParams(gfx::FontRenderParamsQuery(), nullptr));
-  prefs->should_antialias_text = params->antialiasing;
-  prefs->use_subpixel_positioning = params->subpixel_positioning;
-  prefs->hinting = params->hinting;
-  prefs->use_autohinter = params->autohinter;
-  prefs->use_bitmaps = params->use_bitmaps;
-  prefs->subpixel_rendering = params->subpixel_rendering;
-}
-
-}  // namespace content
diff --git a/content/renderer/loader/web_url_loader_impl.cc b/content/renderer/loader/web_url_loader_impl.cc
index ef22a325..ec9e4e8 100644
--- a/content/renderer/loader/web_url_loader_impl.cc
+++ b/content/renderer/loader/web_url_loader_impl.cc
@@ -176,37 +176,6 @@
   }
 }
 
-// Extracts info from a data scheme URL |url| into |info| and |data|. Returns
-// net::OK if successful. Returns a net error code otherwise.
-int GetInfoFromDataURL(const GURL& url,
-                       network::ResourceResponseInfo* info,
-                       std::string* data) {
-  // Assure same time for all time fields of data: URLs.
-  Time now = Time::Now();
-  info->load_timing.request_start = TimeTicks::Now();
-  info->load_timing.request_start_time = now;
-  info->request_time = now;
-  info->response_time = now;
-
-  std::string mime_type;
-  std::string charset;
-  scoped_refptr<net::HttpResponseHeaders> headers(
-      new net::HttpResponseHeaders(std::string()));
-  int result = net::URLRequestDataJob::BuildResponse(
-      url, &mime_type, &charset, data, headers.get());
-  if (result != net::OK)
-    return result;
-
-  info->headers = headers;
-  info->mime_type.swap(mime_type);
-  info->charset.swap(charset);
-  info->content_length = data->length();
-  info->encoded_data_length = 0;
-  info->encoded_body_length = 0;
-
-  return net::OK;
-}
-
 // Convert a net::SignedCertificateTimestampAndStatus object to a
 // blink::WebURLResponse::SignedCertificateTimestamp object.
 blink::WebURLResponse::SignedCertificateTimestamp NetSCTToBlinkSCT(
@@ -451,7 +420,6 @@
   void CancelBodyStreaming();
   // We can optimize the handling of data URLs in most cases.
   bool CanHandleDataURLRequestLocally(const WebURLRequest& request) const;
-  void HandleDataURL();
 
   void OnBodyAvailable(MojoResult, const mojo::HandleSignalsState&);
   void OnBodyHasBeenRead(uint32_t read_bytes);
@@ -483,7 +451,7 @@
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
   std::unique_ptr<SharedMemoryDataConsumerHandle::Writer> body_stream_writer_;
   mojom::KeepAliveHandlePtr keep_alive_handle_;
-  enum DeferState {NOT_DEFERRING, SHOULD_DEFER, DEFERRED_DATA};
+  enum DeferState { NOT_DEFERRING, SHOULD_DEFER };
   DeferState defers_loading_;
   int request_id_;
 
@@ -606,10 +574,6 @@
   if (value && defers_loading_ == NOT_DEFERRING) {
     defers_loading_ = SHOULD_DEFER;
   } else if (!value && defers_loading_ != NOT_DEFERRING) {
-    if (defers_loading_ == DEFERRED_DATA) {
-      task_runner_->PostTask(FROM_HERE,
-                             base::BindOnce(&Context::HandleDataURL, this));
-    }
     defers_loading_ = NOT_DEFERRING;
 
     if (body_watcher_.IsWatching()) {
@@ -644,19 +608,9 @@
   report_raw_headers_ = request.ReportRawHeaders();
   pass_response_pipe_to_client_ = request.PassResponsePipeToClient();
 
-  if (CanHandleDataURLRequestLocally(request)) {
-    if (sync_load_response) {
-      // This is a sync load. Do the work now.
-      sync_load_response->url = url_;
-      sync_load_response->error_code =
-          GetInfoFromDataURL(sync_load_response->url, &sync_load_response->info,
-                             &sync_load_response->data);
-    } else {
-      task_runner_->PostTask(FROM_HERE,
-                             base::BindOnce(&Context::HandleDataURL, this));
-    }
-    return;
-  }
+  // TODO(https://crbug.com/923779): Remove this once after we can confirm it's
+  // working well.
+  CHECK(!CanHandleDataURLRequestLocally(request));
 
   std::unique_ptr<NavigationResponseOverrideParameters> response_override;
   if (request.GetExtraData()) {
@@ -1034,31 +988,6 @@
   return false;
 }
 
-void WebURLLoaderImpl::Context::HandleDataURL() {
-  DCHECK_NE(defers_loading_, DEFERRED_DATA);
-  if (defers_loading_ == SHOULD_DEFER) {
-      defers_loading_ = DEFERRED_DATA;
-      return;
-  }
-
-  network::ResourceResponseInfo info;
-  std::string data;
-
-  int error_code = GetInfoFromDataURL(url_, &info, &data);
-
-  if (error_code == net::OK) {
-    OnReceivedResponse(info);
-    auto size = data.size();
-    if (size != 0)
-      OnReceivedData(std::make_unique<FixedReceivedData>(data.data(), size));
-  }
-
-  network::URLLoaderCompletionStatus status(error_code);
-  status.encoded_body_length = data.size();
-  status.decoded_body_length = data.size();
-  OnCompletedRequest(status);
-}
-
 void WebURLLoaderImpl::Context::OnBodyAvailable(
     MojoResult,
     const mojo::HandleSignalsState&) {
diff --git a/content/renderer/loader/web_url_loader_impl_unittest.cc b/content/renderer/loader/web_url_loader_impl_unittest.cc
index be11f44..74b75758d 100644
--- a/content/renderer/loader/web_url_loader_impl_unittest.cc
+++ b/content/renderer/loader/web_url_loader_impl_unittest.cc
@@ -503,90 +503,6 @@
   DoFailRequest();
 }
 
-TEST_P(WebURLLoaderImplTest, DeleteBeforeResponseDataURL) {
-  blink::WebURLRequest request(GURL("data:text/html;charset=utf-8,blah!"));
-  client()->loader()->LoadAsynchronously(request, client());
-  client()->DeleteLoader();
-  base::RunLoop().RunUntilIdle();
-  EXPECT_FALSE(client()->did_receive_response());
-}
-
-// Data URL tests.
-
-TEST_P(WebURLLoaderImplTest, DataURL) {
-  blink::WebURLRequest request(GURL("data:text/html;charset=utf-8,blah!"));
-  client()->loader()->LoadAsynchronously(request, client());
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ("blah!", client()->received_data());
-  EXPECT_TRUE(client()->did_finish());
-  EXPECT_FALSE(client()->error());
-}
-
-TEST_P(WebURLLoaderImplTest, DataURLDeleteOnReceiveResponse) {
-  blink::WebURLRequest request(GURL("data:text/html;charset=utf-8,blah!"));
-  client()->set_delete_on_receive_response();
-  client()->loader()->LoadAsynchronously(request, client());
-  base::RunLoop().RunUntilIdle();
-  EXPECT_TRUE(client()->did_receive_response());
-  EXPECT_EQ("", client()->received_data());
-  EXPECT_FALSE(client()->did_finish());
-}
-
-TEST_P(WebURLLoaderImplTest, DataURLDeleteOnReceiveData) {
-  blink::WebURLRequest request(GURL("data:text/html;charset=utf-8,blah!"));
-  client()->set_delete_on_receive_data();
-  client()->loader()->LoadAsynchronously(request, client());
-  base::RunLoop().RunUntilIdle();
-  EXPECT_TRUE(client()->did_receive_response());
-  EXPECT_EQ("blah!", client()->received_data());
-  EXPECT_FALSE(client()->did_finish());
-}
-
-TEST_P(WebURLLoaderImplTest, DataURLDeleteOnFinish) {
-  blink::WebURLRequest request(GURL("data:text/html;charset=utf-8,blah!"));
-  client()->set_delete_on_finish();
-  client()->loader()->LoadAsynchronously(request, client());
-  base::RunLoop().RunUntilIdle();
-  EXPECT_TRUE(client()->did_receive_response());
-  EXPECT_EQ("blah!", client()->received_data());
-  EXPECT_TRUE(client()->did_finish());
-}
-
-TEST_P(WebURLLoaderImplTest, DataURLDefersLoading) {
-  blink::WebURLRequest request(GURL("data:text/html;charset=utf-8,blah!"));
-  client()->loader()->LoadAsynchronously(request, client());
-
-  // setDefersLoading() might be called with either false or true in no
-  // specific order. The user of the API will not have sufficient information
-  // about the WebURLLoader's internal state, so the latter gracefully needs to
-  // handle calling setDefersLoading any number of times with any values from
-  // any point in time.
-
-  client()->loader()->SetDefersLoading(false);
-  client()->loader()->SetDefersLoading(true);
-  client()->loader()->SetDefersLoading(true);
-  base::RunLoop().RunUntilIdle();
-  EXPECT_FALSE(client()->did_finish());
-
-  client()->loader()->SetDefersLoading(false);
-  client()->loader()->SetDefersLoading(true);
-  base::RunLoop().RunUntilIdle();
-  EXPECT_FALSE(client()->did_finish());
-
-  client()->loader()->SetDefersLoading(false);
-  base::RunLoop().RunUntilIdle();
-  EXPECT_TRUE(client()->did_finish());
-
-  client()->loader()->SetDefersLoading(true);
-  client()->loader()->SetDefersLoading(false);
-  client()->loader()->SetDefersLoading(false);
-  base::RunLoop().RunUntilIdle();
-  EXPECT_TRUE(client()->did_finish());
-
-  EXPECT_EQ("blah!", client()->received_data());
-  EXPECT_FALSE(client()->error());
-}
-
 TEST_P(WebURLLoaderImplTest, DefersLoadingBeforeStart) {
   client()->loader()->SetDefersLoading(true);
   EXPECT_FALSE(dispatcher()->defers_loading());
diff --git a/content/renderer/media/stream/webmediaplayer_ms_unittest.cc b/content/renderer/media/stream/webmediaplayer_ms_unittest.cc
index 534b792..6023862 100644
--- a/content/renderer/media/stream/webmediaplayer_ms_unittest.cc
+++ b/content/renderer/media/stream/webmediaplayer_ms_unittest.cc
@@ -572,12 +572,6 @@
   void RemoveTextTrack(blink::WebInbandTextTrack*) override {}
   void MediaSourceOpened(blink::WebMediaSource*) override {}
   void RequestSeek(double) override {}
-  void RemoteRouteAvailabilityChanged(
-      blink::WebRemotePlaybackAvailability) override {}
-  void ConnectedToRemoteDevice() override {}
-  void DisconnectedFromRemoteDevice() override {}
-  void CancelledRemotePlaybackRequest() override {}
-  void RemotePlaybackStarted() override {}
   void RemotePlaybackCompatibilityChanged(const blink::WebURL& url,
                                           bool is_compatible) override {}
   void OnBecamePersistentVideo(bool) override {}
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 104e48a..79b954f 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -5948,15 +5948,38 @@
   DCHECK(!(was_within_same_document && interface_params));
   UpdateStateForCommit(item, commit_type, transition);
 
+  auto params = MakeDidCommitProvisionalLoadParams(commit_type, transition);
+
+  // If this is a regular commit, not an error page, the URL that was just
+  // committed must match the process lock, if there is one. Verify it here, to
+  // get a stack trace for a bug where this seems to be occurring.
+  // TODO(nasko): Remove this check after we've gathered enough information to
+  // debug issues with browser-side security checks. https://crbug.com/931895.
+  RenderThreadImpl* render_thread = RenderThreadImpl::current();
+  const GURL* lock_url =
+      render_thread ? render_thread->site_lock_url() : nullptr;
+  if (!params->url_is_unreachable && lock_url &&
+      lock_url->scheme() == params->url.scheme() &&
+      lock_url->SchemeIsHTTPOrHTTPS() && !params->url.HostIsIPAddress() &&
+      !lock_url->HostIsIPAddress()) {
+    std::string lock_domain =
+        net::registry_controlled_domains::GetDomainAndRegistry(
+            lock_url->host(),
+            net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
+    std::string commit_domain =
+        net::registry_controlled_domains::GetDomainAndRegistry(
+            params->url.host(),
+            net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
+    CHECK_EQ(lock_domain, commit_domain);
+  }
+
   // This invocation must precede any calls to allowScripts(), allowImages(), or
   // allowPlugins() for the new page. This ensures that when these functions
   // send ViewHostMsg_ContentBlocked messages, those arrive after the browser
   // process has already been informed of the provisional load committing.
   if (was_within_same_document) {
-    GetFrameHost()->DidCommitSameDocumentNavigation(
-        MakeDidCommitProvisionalLoadParams(commit_type, transition));
+    GetFrameHost()->DidCommitSameDocumentNavigation(std::move(params));
   } else {
-    auto params = MakeDidCommitProvisionalLoadParams(commit_type, transition);
     NavigationState* navigation_state =
         NavigationState::FromDocumentLoader(frame_->GetDocumentLoader());
     if (navigation_state->uses_per_navigation_mojo_interface()) {
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
index b5669f9..35759ddd 100644
--- a/content/renderer/render_thread_impl.cc
+++ b/content/renderer/render_thread_impl.cc
@@ -1722,9 +1722,10 @@
       base::TimeDelta::FromMinutes(90));
 }
 
-void RenderThreadImpl::SetIsLockedToSite() {
+void RenderThreadImpl::SetIsLockedToSite(const GURL& lock_url) {
   DCHECK(blink_platform_impl_);
   blink_platform_impl_->SetIsLockedToSite();
+  site_lock_url_ = std::make_unique<GURL>(lock_url);
 }
 
 void RenderThreadImpl::EnableV8LowMemoryMode() {
diff --git a/content/renderer/render_thread_impl.h b/content/renderer/render_thread_impl.h
index 579131f..7b0b08e1 100644
--- a/content/renderer/render_thread_impl.h
+++ b/content/renderer/render_thread_impl.h
@@ -510,6 +510,10 @@
     video_frame_compositor_task_runner_ = task_runner;
   }
 
+  // TODO(nasko): Remove after we've gathered enough information to debug issues
+  // with browser-side security checks. https://crbug.com/931895.
+  const GURL* site_lock_url() { return site_lock_url_.get(); }
+
  private:
   void OnProcessFinalRelease() override;
   // IPC::Listener
@@ -565,7 +569,7 @@
   void SetProcessBackgrounded(bool backgrounded) override;
   void SetSchedulerKeepActive(bool keep_active) override;
   void ProcessPurgeAndSuspend() override;
-  void SetIsLockedToSite() override;
+  void SetIsLockedToSite(const GURL& lock_url) override;
   void EnableV8LowMemoryMode() override;
 
   void OnMemoryPressure(
@@ -767,6 +771,11 @@
   mojo::Binding<viz::mojom::CompositingModeWatcher>
       compositing_mode_watcher_binding_;
 
+  // TODO(nasko): Temporary diagnostic member, holding the site URL this process
+  // is locked to. Remove after we've gathered enough information to
+  // debug issues with browser-side security checks. https://crbug.com/931895.
+  std::unique_ptr<GURL> site_lock_url_;
+
   base::WeakPtrFactory<RenderThreadImpl> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(RenderThreadImpl);
diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc
index 2d0908f..89983ab 100644
--- a/content/renderer/render_view_impl.cc
+++ b/content/renderer/render_view_impl.cc
@@ -1935,6 +1935,16 @@
   }
 #endif  // BUILDFLAG(USE_DEFAULT_RENDER_THEME)
 
+#if defined(OS_ANDROID)
+  if (renderer_prefs.use_custom_colors)
+    blink::SetFocusRingColor(renderer_prefs.focus_ring_color);
+
+  blink::SetMinimumStrokeWidthForFocusRing(
+      renderer_prefs.minimum_stroke_width_for_focus_ring);
+
+  blink::SetIsFocusRingOutset(renderer_prefs.is_focus_ring_outset);
+#endif
+
   if (webview() &&
       old_accept_languages != renderer_preferences_.accept_languages) {
     webview()->AcceptLanguagesChanged();
diff --git a/content/shell/browser/web_test/web_test_content_browser_client.cc b/content/shell/browser/web_test/web_test_content_browser_client.cc
index 2c52e0e4..cb39a60 100644
--- a/content/shell/browser/web_test/web_test_content_browser_client.cc
+++ b/content/shell/browser/web_test/web_test_content_browser_client.cc
@@ -79,6 +79,7 @@
   void SetAlwaysHidePlayPauseButton(bool is_visible) override {}
   void SetSkipAdButtonVisibility(bool is_visible) override {}
   void SetNextTrackButtonVisibility(bool is_visible) override {}
+  void SetPreviousTrackButtonVisibility(bool is_visible) override {}
   ui::Layer* GetWindowBackgroundLayer() override { return nullptr; }
   ui::Layer* GetVideoLayer() override { return nullptr; }
   gfx::Rect GetVideoBounds() override { return gfx::Rect(); }
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 7e9da36..2ddd6a03 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1697,6 +1697,7 @@
     "../browser/worker_host/shared_worker_host_unittest.cc",
     "../browser/worker_host/shared_worker_instance_unittest.cc",
     "../browser/worker_host/shared_worker_service_impl_unittest.cc",
+    "../browser/worker_host/worker_script_loader_factory_unittest.cc",
     "../child/blink_platform_impl_unittest.cc",
     "../child/dwrite_font_proxy/dwrite_font_proxy_win_unittest.cc",
     "../child/dwrite_font_proxy/font_fallback_win_unittest.cc",
diff --git a/content/test/test_background_sync_context.cc b/content/test/test_background_sync_context.cc
index 8b8e56a5..82d4029 100644
--- a/content/test/test_background_sync_context.cc
+++ b/content/test/test_background_sync_context.cc
@@ -4,7 +4,8 @@
 
 #include "content/test/test_background_sync_context.h"
 
-#include "base/memory/ptr_util.h"
+#include <memory>
+
 #include "content/browser/service_worker/service_worker_context_wrapper.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/test/test_background_sync_manager.h"
@@ -16,9 +17,8 @@
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   DCHECK(!background_sync_manager());
 
-  TestBackgroundSyncManager* manager = new TestBackgroundSyncManager(context);
   set_background_sync_manager_for_testing(
-      base::WrapUnique<BackgroundSyncManager>(manager));
+      std::make_unique<TestBackgroundSyncManager>(context));
 }
 
 }  // namespace content
diff --git a/fuchsia/engine/browser/frame_impl.cc b/fuchsia/engine/browser/frame_impl.cc
index 2403e178..64a3131 100644
--- a/fuchsia/engine/browser/frame_impl.cc
+++ b/fuchsia/engine/browser/frame_impl.cc
@@ -16,8 +16,8 @@
 #include "content/public/browser/message_port_provider.h"
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/renderer_preferences_util.h"
 #include "content/public/browser/web_contents.h"
-#include "content/public/common/renderer_preferences_util.h"
 #include "content/public/common/was_activated_option.h"
 #include "fuchsia/base/mem_buffer_util.h"
 #include "fuchsia/engine/browser/context_impl.h"
diff --git a/google_apis/drive/files_list_request_runner.cc b/google_apis/drive/files_list_request_runner.cc
index 3b7d9f2..8b25e1c 100644
--- a/google_apis/drive/files_list_request_runner.cc
+++ b/google_apis/drive/files_list_request_runner.cc
@@ -33,8 +33,6 @@
     const std::string& q,
     const std::string& fields,
     const FileListCallback& callback) {
-  UMA_HISTOGRAM_COUNTS_1000("Drive.FilesListRequestRunner.MaxResults",
-                            max_results);
   base::Closure* const cancel_callback = new base::Closure;
   std::unique_ptr<drive::FilesListRequest> request =
       std::make_unique<drive::FilesListRequest>(
@@ -74,8 +72,6 @@
   if (!request_completed_callback_for_testing_.is_null())
     request_completed_callback_for_testing_.Run();
 
-  base::UmaHistogramSparse("Drive.FilesListRequestRunner.ApiErrorCode", error);
-
   if (error == google_apis::DRIVE_RESPONSE_TOO_LARGE && max_results > 1) {
     CreateAndStartWithSizeBackoff(max_results / 2, corpora, team_drive_id, q,
                                   fields, callback);
diff --git a/google_apis/gaia/oauth2_token_service_delegate.h b/google_apis/gaia/oauth2_token_service_delegate.h
index e49edad..a8c4135 100644
--- a/google_apis/gaia/oauth2_token_service_delegate.h
+++ b/google_apis/gaia/oauth2_token_service_delegate.h
@@ -126,6 +126,12 @@
   // and false otherwise.
   virtual bool FixRequestErrorIfPossible();
 
+#if defined(OS_IOS)
+  // Triggers platform specific implementation for IOS to add a given account
+  // to the token service from a system account.
+  virtual void AddAccountFromSystem(const std::string& account_id) {}
+#endif
+
 #if defined(OS_ANDROID) || defined(OS_IOS)
   // Triggers platform specific implementation for Android and IOS to reload
   // accounts from system.
diff --git a/ios/chrome/browser/about_flags.mm b/ios/chrome/browser/about_flags.mm
index 23ceb76..f3c948d 100644
--- a/ios/chrome/browser/about_flags.mm
+++ b/ios/chrome/browser/about_flags.mm
@@ -558,6 +558,9 @@
      flag_descriptions::kBrowserContainerKeepsContentViewName,
      flag_descriptions::kBrowserContainerKeepsContentViewDescription,
      flags_ui::kOsIos, FEATURE_VALUE_TYPE(kBrowserContainerKeepsContentView)},
+    {"web-clear-browsing-data", flag_descriptions::kWebClearBrowsingDataName,
+     flag_descriptions::kWebClearBrowsingDataDescription, flags_ui::kOsIos,
+     FEATURE_VALUE_TYPE(experimental_flags::kWebClearBrowsingData)},
 };
 
 // Add all switches from experimental flags to |command_line|.
diff --git a/ios/chrome/browser/autofill/form_input_accessory_view_handler.mm b/ios/chrome/browser/autofill/form_input_accessory_view_handler.mm
index 4781820..b8fce9c 100644
--- a/ios/chrome/browser/autofill/form_input_accessory_view_handler.mm
+++ b/ios/chrome/browser/autofill/form_input_accessory_view_handler.mm
@@ -226,7 +226,7 @@
   NSString* actionName = autofill::kFormSuggestionAssistButtonDone;
   BOOL performedAction = [self executeFormAssistAction:actionName];
 
-  if (!performedAction) {
+  if (!performedAction && [_lastFocusFormActivityWebFrameID length]) {
     // We could not find the built-in form assist controls, so try to focus
     // the next or previous control using JavaScript.
     [_JSSuggestionManager
diff --git a/ios/chrome/browser/autofill/form_suggestion_controller_unittest.mm b/ios/chrome/browser/autofill/form_suggestion_controller_unittest.mm
index 3287aa4e..3844849 100644
--- a/ios/chrome/browser/autofill/form_suggestion_controller_unittest.mm
+++ b/ios/chrome/browser/autofill/form_suggestion_controller_unittest.mm
@@ -20,6 +20,7 @@
 #import "ios/chrome/browser/ui/autofill/form_input_accessory_mediator.h"
 #include "ios/chrome/browser/ui/util/ui_util.h"
 #import "ios/web/public/navigation_manager.h"
+#include "ios/web/public/test/fakes/fake_web_frame.h"
 #import "ios/web/public/test/fakes/test_web_state.h"
 #include "ios/web/public/test/test_web_thread_bundle.h"
 #import "ios/web/public/web_state/ui/crw_web_view_proxy.h"
@@ -255,7 +256,9 @@
 TEST_F(FormSuggestionControllerTest,
        PageLoadShouldRestoreKeyboardAccessoryViewAndInjectJavaScript) {
   SetUpController(@[ [TestSuggestionProvider providerWithSuggestions] ]);
-  test_web_state_.SetCurrentURL(GURL("http://foo.com"));
+  GURL url("http://foo.com");
+  test_web_state_.SetCurrentURL(url);
+  web::FakeWebFrame main_frame("main_frame", /*is_main_frame=*/true, url);
 
   // Trigger form activity, which should set up the suggestions view.
   autofill::FormActivityParams params;
@@ -265,8 +268,7 @@
   params.type = "type";
   params.value = "value";
   params.input_missing = false;
-  test_form_activity_tab_helper_.FormActivityRegistered(
-      /*sender_frame*/ nullptr, params);
+  test_form_activity_tab_helper_.FormActivityRegistered(&main_frame, params);
   EXPECT_TRUE(received_suggestions_.count);
 
   // Trigger another page load. The suggestions should not be present.
@@ -277,6 +279,9 @@
 // Tests that "blur" events are ignored.
 TEST_F(FormSuggestionControllerTest, FormActivityBlurShouldBeIgnored) {
   SetUpController(@[ [TestSuggestionProvider providerWithSuggestions] ]);
+  GURL url("http://foo.com");
+  test_web_state_.SetCurrentURL(url);
+  web::FakeWebFrame main_frame("main_frame", true, url);
 
   autofill::FormActivityParams params;
   params.form_name = "form";
@@ -285,8 +290,7 @@
   params.type = "blur";  // blur!
   params.value = "value";
   params.input_missing = false;
-  test_form_activity_tab_helper_.FormActivityRegistered(
-      /*sender_frame*/ nullptr, params);
+  test_form_activity_tab_helper_.FormActivityRegistered(&main_frame, params);
   EXPECT_FALSE(received_suggestions_.count);
 }
 
@@ -295,7 +299,10 @@
        FormActivityShouldRetrieveSuggestions_NoProvidersAvailable) {
   // Set up the controller without any providers.
   SetUpController(@[]);
-  test_web_state_.SetCurrentURL(GURL("http://foo.com"));
+  GURL url("http://foo.com");
+  test_web_state_.SetCurrentURL(url);
+  web::FakeWebFrame main_frame("main_frame", true, url);
+
   autofill::FormActivityParams params;
   params.form_name = "form";
   params.field_identifier = "field_id";
@@ -303,8 +310,7 @@
   params.type = "type";
   params.value = "value";
   params.input_missing = false;
-  test_form_activity_tab_helper_.FormActivityRegistered(
-      /*sender_frame*/ nullptr, params);
+  test_form_activity_tab_helper_.FormActivityRegistered(&main_frame, params);
 
   // The suggestions should be empty.
   EXPECT_TRUE(received_suggestions_);
@@ -322,7 +328,9 @@
   TestSuggestionProvider* provider2 =
       [[TestSuggestionProvider alloc] initWithSuggestions:@[]];
   SetUpController(@[ provider1, provider2 ]);
-  test_web_state_.SetCurrentURL(GURL("http://foo.com"));
+  GURL url("http://foo.com");
+  test_web_state_.SetCurrentURL(url);
+  web::FakeWebFrame main_frame("main_frame", true, url);
 
   autofill::FormActivityParams params;
   params.form_name = "form";
@@ -331,8 +339,7 @@
   params.type = "type";
   params.value = "value";
   params.input_missing = false;
-  test_form_activity_tab_helper_.FormActivityRegistered(
-      /*sender_frame*/ nullptr, params);
+  test_form_activity_tab_helper_.FormActivityRegistered(&main_frame, params);
 
   // The providers should each be asked if they have suggestions for the
   // form in question.
@@ -370,7 +377,9 @@
   TestSuggestionProvider* provider2 =
       [[TestSuggestionProvider alloc] initWithSuggestions:@[]];
   SetUpController(@[ provider1, provider2 ]);
-  test_web_state_.SetCurrentURL(GURL("http://foo.com"));
+  GURL url("http://foo.com");
+  test_web_state_.SetCurrentURL(url);
+  web::FakeWebFrame main_frame("main_frame", true, url);
 
   autofill::FormActivityParams params;
   params.form_name = "form";
@@ -379,8 +388,7 @@
   params.type = "type";
   params.value = "value";
   params.input_missing = false;
-  test_form_activity_tab_helper_.FormActivityRegistered(
-      /*sender_frame*/ nullptr, params);
+  test_form_activity_tab_helper_.FormActivityRegistered(&main_frame, params);
 
   // Since the first provider has suggestions available, it and only it
   // should have been asked.
@@ -410,7 +418,10 @@
   TestSuggestionProvider* provider =
       [[TestSuggestionProvider alloc] initWithSuggestions:suggestions];
   SetUpController(@[ provider ]);
-  test_web_state_.SetCurrentURL(GURL("http://foo.com"));
+  GURL url("http://foo.com");
+  test_web_state_.SetCurrentURL(url);
+  web::FakeWebFrame main_frame("main_frame", true, url);
+
   autofill::FormActivityParams params;
   params.form_name = "form";
   params.field_identifier = "field_id";
@@ -419,8 +430,7 @@
   params.value = "value";
   params.frame_id = "frame_id";
   params.input_missing = false;
-  test_form_activity_tab_helper_.FormActivityRegistered(
-      /*sender_frame*/ nullptr, params);
+  test_form_activity_tab_helper_.FormActivityRegistered(&main_frame, params);
 
   // Selecting a suggestion should notify the delegate.
   [suggestion_controller_ didSelectSuggestion:suggestions[0]];
diff --git a/ios/chrome/browser/autofill/js_suggestion_manager_unittest.mm b/ios/chrome/browser/autofill/js_suggestion_manager_unittest.mm
index 22457ba..d090d2b 100644
--- a/ios/chrome/browser/autofill/js_suggestion_manager_unittest.mm
+++ b/ios/chrome/browser/autofill/js_suggestion_manager_unittest.mm
@@ -40,6 +40,13 @@
   NSString* GetActiveElementName() {
     return ExecuteJavaScript(@"document.activeElement.name");
   }
+  // Waits until the active element is |name|.
+  BOOL WaitUntilElementSelected(NSString* name) {
+    return base::test::ios::WaitUntilConditionOrTimeout(
+        base::test::ios::kWaitForJSCompletionTimeout, ^bool {
+          return [GetActiveElementName() isEqualToString:name];
+        });
+  }
   JsSuggestionManager* manager_;
 };
 
@@ -230,7 +237,7 @@
   ExecuteJavaScript(@"document.getElementsByName('firstname')[0].focus()");
 
   [manager_ selectNextElementInFrameWithID:GetFrameIdForMainFrame()];
-  EXPECT_NSEQ(@"lastname", GetActiveElementName());
+  EXPECT_TRUE(WaitUntilElementSelected(@"lastname"));
   __block BOOL block_was_called = NO;
   [manager_
       fetchPreviousAndNextElementsPresenceInFrameWithID:GetFrameIdForMainFrame()
@@ -245,9 +252,9 @@
     return block_was_called;
   });
   [manager_ selectNextElementInFrameWithID:GetFrameIdForMainFrame()];
-  EXPECT_NSEQ(@"email", GetActiveElementName());
+  EXPECT_TRUE(WaitUntilElementSelected(@"email"));
   [manager_ selectPreviousElementInFrameWithID:GetFrameIdForMainFrame()];
-  EXPECT_NSEQ(@"lastname", GetActiveElementName());
+  EXPECT_TRUE(WaitUntilElementSelected(@"lastname"));
 }
 
 void JsSuggestionManagerTest::SequentialNavigationSkipCheck(NSString* attribute,
@@ -262,11 +269,10 @@
   ExecuteJavaScript(@"document.getElementsByName('firstname')[0].focus()");
   EXPECT_NSEQ(@"firstname", GetActiveElementName());
   [manager_ selectNextElementInFrameWithID:GetFrameIdForMainFrame()];
-  NSString* activeElementNameJS = GetActiveElementName();
   if (shouldSkip)
-    EXPECT_NSEQ(@"lastname", activeElementNameJS);
+    EXPECT_TRUE(WaitUntilElementSelected(@"lastname"));
   else
-    EXPECT_NSEQ(@"middlename", activeElementNameJS);
+    EXPECT_TRUE(WaitUntilElementSelected(@"middlename"));
 }
 
 TEST_F(JsSuggestionManagerTest, SequentialNavigationNoSkipText) {
@@ -360,6 +366,7 @@
             GetFrameIdForMainFrame()
                                         completionHandler:completionHandler];
     base::test::ios::WaitUntilCondition(^bool() {
+      base::RunLoop().RunUntilIdle();
       return block_was_called;
     });
   }
diff --git a/ios/chrome/browser/browsing_data/browsing_data_remover_impl.mm b/ios/chrome/browser/browsing_data/browsing_data_remover_impl.mm
index b8d71347..5011d77 100644
--- a/ios/chrome/browser/browsing_data/browsing_data_remover_impl.mm
+++ b/ios/chrome/browser/browsing_data/browsing_data_remover_impl.mm
@@ -42,6 +42,7 @@
 #include "ios/chrome/browser/bookmarks/bookmark_remover_helper.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #include "ios/chrome/browser/browsing_data/browsing_data_remove_mask.h"
+#include "ios/chrome/browser/experimental_flags.h"
 #include "ios/chrome/browser/history/history_service_factory.h"
 #include "ios/chrome/browser/history/web_history_service_factory.h"
 #include "ios/chrome/browser/ios_chrome_io_thread.h"
@@ -57,6 +58,7 @@
 #include "ios/chrome/browser/ui/external_file_remover_factory.h"
 #include "ios/chrome/browser/web_data_service_factory.h"
 #include "ios/net/http_cache_helper.h"
+#import "ios/web/public/browsing_data_removing_util.h"
 #include "ios/web/public/web_task_traits.h"
 #include "ios/web/public/web_thread.h"
 #import "ios/web/public/web_view_creation_util.h"
@@ -546,6 +548,38 @@
     base::Time delete_begin,
     base::Time delete_end,
     BrowsingDataRemoveMask mask) {
+  if (base::FeatureList::IsEnabled(experimental_flags::kWebClearBrowsingData)) {
+    web::ClearBrowsingDataMask types =
+        web::ClearBrowsingDataMask::kRemoveNothing;
+    if (IsRemoveDataMaskSet(mask, BrowsingDataRemoveMask::REMOVE_APPCACHE)) {
+      types |= web::ClearBrowsingDataMask::kRemoveAppCache;
+    }
+    if (IsRemoveDataMaskSet(mask, BrowsingDataRemoveMask::REMOVE_COOKIES)) {
+      types |= web::ClearBrowsingDataMask::kRemoveCookies;
+    }
+    if (IsRemoveDataMaskSet(mask, BrowsingDataRemoveMask::REMOVE_INDEXEDDB)) {
+      types |= web::ClearBrowsingDataMask::kRemoveIndexedDB;
+    }
+    if (IsRemoveDataMaskSet(mask,
+                            BrowsingDataRemoveMask::REMOVE_LOCAL_STORAGE)) {
+      types |= web::ClearBrowsingDataMask::kRemoveLocalStorage;
+    }
+    if (IsRemoveDataMaskSet(mask, BrowsingDataRemoveMask::REMOVE_WEBSQL)) {
+      types |= web::ClearBrowsingDataMask::kRemoveWebSQL;
+    }
+    if (IsRemoveDataMaskSet(mask,
+                            BrowsingDataRemoveMask::REMOVE_CACHE_STORAGE)) {
+      types |= web::ClearBrowsingDataMask::kRemoveCacheStorage;
+    }
+    if (IsRemoveDataMaskSet(mask,
+                            BrowsingDataRemoveMask::REMOVE_VISITED_LINKS)) {
+      types |= web::ClearBrowsingDataMask::kRemoveVisitedLinks;
+    }
+
+    web::ClearBrowsingData(browser_state_, types);
+    return;
+  }
+
   // Converts browsing data types from BrowsingDataRemoveMask to
   // WKWebsiteDataStore strings.
   NSMutableSet* data_types_to_remove = [[NSMutableSet alloc] init];
diff --git a/ios/chrome/browser/experimental_flags.h b/ios/chrome/browser/experimental_flags.h
index 681bf39..85c9a7a 100644
--- a/ios/chrome/browser/experimental_flags.h
+++ b/ios/chrome/browser/experimental_flags.h
@@ -18,6 +18,10 @@
 // ExternalFileController.
 extern const base::Feature kExternalFilesLoadedInWebState;
 
+// Feature to use the clear browsing data from web instead of the one from
+// chrome.
+extern const base::Feature kWebClearBrowsingData;
+
 enum GaiaEnvironment {
   GAIA_ENVIRONMENT_PROD,
   GAIA_ENVIRONMENT_STAGING,
diff --git a/ios/chrome/browser/experimental_flags.mm b/ios/chrome/browser/experimental_flags.mm
index e072e71..b2731f3 100644
--- a/ios/chrome/browser/experimental_flags.mm
+++ b/ios/chrome/browser/experimental_flags.mm
@@ -50,6 +50,9 @@
 const base::Feature kExternalFilesLoadedInWebState{
     "ExternalFilesLoadedInWebState", base::FEATURE_ENABLED_BY_DEFAULT};
 
+const base::Feature kWebClearBrowsingData{"WebClearBrowsingData",
+                                          base::FEATURE_DISABLED_BY_DEFAULT};
+
 bool AlwaysDisplayFirstRun() {
   return
       [[NSUserDefaults standardUserDefaults] boolForKey:kFirstRunForceEnabled];
diff --git a/ios/chrome/browser/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/ios_chrome_flag_descriptions.cc
index ca26dcd8..3d5e45e 100644
--- a/ios/chrome/browser/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/ios_chrome_flag_descriptions.cc
@@ -415,6 +415,10 @@
 const char kUseDdljsonApiDescription[] =
     "Enables the new ddljson API to fetch Doodles for the NTP.";
 
+const char kWebClearBrowsingDataName[] = "Web-API for browsing data";
+const char kWebClearBrowsingDataDescription[] =
+    "When enabled the Clear Browsing Data feature is using the web API.";
+
 const char kWebFrameMessagingName[] = "Web Frame Messaging";
 const char kWebFrameMessagingDescription[] =
     "When enabled, API will be injected into webpages to allow sending messages"
diff --git a/ios/chrome/browser/ios_chrome_flag_descriptions.h b/ios/chrome/browser/ios_chrome_flag_descriptions.h
index d532fc1..2f2a0aa 100644
--- a/ios/chrome/browser/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/ios_chrome_flag_descriptions.h
@@ -345,6 +345,11 @@
 extern const char kUseDdljsonApiName[];
 extern const char kUseDdljsonApiDescription[];
 
+// Title and description for the flag to use the Clear browsing data API from
+// web.
+extern const char kWebClearBrowsingDataName[];
+extern const char kWebClearBrowsingDataDescription[];
+
 // Title and description for the flag to enable web frame messaging.
 extern const char kWebFrameMessagingName[];
 extern const char kWebFrameMessagingDescription[];
diff --git a/ios/chrome/browser/passwords/password_controller.mm b/ios/chrome/browser/passwords/password_controller.mm
index e836f715..155716b6 100644
--- a/ios/chrome/browser/passwords/password_controller.mm
+++ b/ios/chrome/browser/passwords/password_controller.mm
@@ -101,6 +101,9 @@
   COUNT
 };
 
+// Password is considered not generated when user edits it below 4 characters.
+constexpr int kMinimumLengthForEditedPassword = 4;
+
 // Duration for notify user auto-sign in dialog being displayed.
 constexpr int kNotifyAutoSigninDuration = 3;  // seconds
 
@@ -134,6 +137,9 @@
 // The action sheet coordinator, if one is currently being shown.
 @property(nonatomic, strong) ActionSheetCoordinator* actionSheetCoordinator;
 
+// Tracks if current password is generated.
+@property(nonatomic, assign) BOOL isPasswordGenerated;
+
 @end
 
 @interface PasswordController ()<FormSuggestionProvider, PasswordFormFiller>
@@ -318,6 +324,7 @@
   _passwordManagerClient.reset();
   _credentialManager.reset();
   _formGenerationData.clear();
+  _isPasswordGenerated = NO;
 }
 
 #pragma mark - FormSuggestionProvider
@@ -350,6 +357,22 @@
                          completion([fieldType isEqualToString:@"password"] ||
                                     suggestionsAvailable);
                        }];
+
+  if (self.isPasswordGenerated &&
+      [self canGeneratePasswordForForm:formName
+                       fieldIdentifier:fieldIdentifier
+                             fieldType:fieldType]) {
+    if (typedValue.length < kMinimumLengthForEditedPassword) {
+      self.isPasswordGenerated = NO;
+      // TODO(crbug.com/886583): call
+      // passwordManager->OnPasswordNoLongerGenerated, but how to get
+      // PasswordForm?
+    } else {
+      [self injectGeneratedPasswordForFormName:formName
+                             generatedPassword:typedValue
+                             completionHandler:nil];
+    }
+  }
 }
 
 - (void)retrieveSuggestionsForForm:(NSString*)formName
@@ -722,29 +745,20 @@
       IsIPadIdiom() ? UIAlertControllerStyleAlert
                     : UIAlertControllerStyleActionSheet;
 
-  auto generatedPasswordInjected = ^(BOOL success) {
-    if (success) {
-      // TODO(crbug.com/886583) do not call presaved if username hasn't been
-      // filled in.
-      // TODO(crbug.com/886583) call _pM::OnPresaveGeneratedPassword once it has
-      // been refactored not to need a full form.
-    }
-    if (completionHandler)
-      completionHandler(YES);
-  };
-
-  auto injectGeneratedPassword = ^{
-    [self.formHelper fillPasswordForm:formName
-                newPasswordIdentifier:newPasswordIdentifier
-            confirmPasswordIdentifier:confirmPasswordIdentifier
-                    generatedPassword:displayPassword
-                    completionHandler:generatedPasswordInjected];
-  };
-
+  __weak PasswordController* weakSelf = self;
   // TODO(crbug.com/886583): i18n
-  [self.actionSheetCoordinator addItemWithTitle:@"Use Suggested Password"
-                                         action:injectGeneratedPassword
-                                          style:UIAlertActionStyleDefault];
+  [self.actionSheetCoordinator
+      addItemWithTitle:@"Use Suggested Password"
+                action:^{
+                  [weakSelf
+                      injectGeneratedPasswordForFormName:formName
+                                   newPasswordIdentifier:newPasswordIdentifier
+                               confirmPasswordIdentifier:
+                                   confirmPasswordIdentifier
+                                       generatedPassword:displayPassword
+                                       completionHandler:completionHandler];
+                }
+                 style:UIAlertActionStyleDefault];
 
   [self.actionSheetCoordinator
       addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
@@ -757,4 +771,44 @@
   [self.actionSheetCoordinator start];
 }
 
+- (void)injectGeneratedPasswordForFormName:(NSString*)formName
+                         generatedPassword:(NSString*)generatedPassword
+                         completionHandler:(void (^)(BOOL))completionHandler {
+  if (![self hasFormForGenerationForFormName:formName])
+    return;
+  const autofill::NewPasswordFormGenerationData form =
+      [self getFormForGenerationFromFormName:formName];
+  NSString* newPasswordIdentifier =
+      SysUTF16ToNSString(form.new_password_element);
+  NSString* confirmPasswordIdentifier =
+      SysUTF16ToNSString(form.confirmation_password_element);
+  [self injectGeneratedPasswordForFormName:formName
+                     newPasswordIdentifier:newPasswordIdentifier
+                 confirmPasswordIdentifier:confirmPasswordIdentifier
+                         generatedPassword:generatedPassword
+                         completionHandler:completionHandler];
+}
+
+- (void)injectGeneratedPasswordForFormName:(NSString*)formName
+                     newPasswordIdentifier:(NSString*)newPasswordIdentifier
+                 confirmPasswordIdentifier:(NSString*)confirmPasswordIdentifier
+                         generatedPassword:(NSString*)generatedPassword
+                         completionHandler:(void (^)(BOOL))completionHandler {
+  auto generatedPasswordInjected = ^(BOOL success) {
+    if (success) {
+      // TODO(crbug.com/886583) call _pM::OnPresaveGeneratedPassword once it has
+      // been refactored not to need a full form.
+      self.isPasswordGenerated = YES;
+    }
+    if (completionHandler)
+      completionHandler(YES);
+  };
+
+  [self.formHelper fillPasswordForm:formName
+              newPasswordIdentifier:newPasswordIdentifier
+          confirmPasswordIdentifier:confirmPasswordIdentifier
+                  generatedPassword:generatedPassword
+                  completionHandler:generatedPasswordInjected];
+}
+
 @end
diff --git a/ios/chrome/browser/passwords/password_controller_js_unittest.mm b/ios/chrome/browser/passwords/password_controller_js_unittest.mm
index 51a17ea9..ed7b265 100644
--- a/ios/chrome/browser/passwords/password_controller_js_unittest.mm
+++ b/ios/chrome/browser/passwords/password_controller_js_unittest.mm
@@ -513,7 +513,7 @@
 // filled with the generated password and that new password field is untouched.
 TEST_F(
     PasswordControllerJsTest,
-    FillPasswordFormWithGeneratedPassword_SucceedsWhenOnlyConfirmPasswordFilled) {
+    FillPasswordFormWithGeneratedPassword_FailsWhenOnlyConfirmPasswordFilled) {
   LoadHtmlAndInject(@"<html>"
                      "  <body>"
                      "    <form name=\"foo\">"
@@ -528,19 +528,16 @@
   NSString* const password = @"abc";
   NSString* const confirmPasswordIdentifier = @"ps2";
   EXPECT_NSEQ(
-      @YES,
-      ExecuteJavaScriptWithFormat(
-          @"__gCrWeb.passwords."
-          @"fillPasswordFormWithGeneratedPassword('%@', '%@', '%@', '%@')",
-          formName, @"", confirmPasswordIdentifier, password));
+      @NO, ExecuteJavaScriptWithFormat(
+               @"__gCrWeb.passwords."
+               @"fillPasswordFormWithGeneratedPassword('%@', '%@', '%@', '%@')",
+               formName, @"", confirmPasswordIdentifier, password));
   EXPECT_NSEQ(@YES, ExecuteJavaScriptWithFormat(
                         @"document.getElementById('ps1').value == '%@'", @""));
-  EXPECT_NSEQ(@YES,
-              ExecuteJavaScriptWithFormat(
-                  @"document.getElementById('ps2').value == '%@'", password));
-  EXPECT_NSEQ(@NO,
-              ExecuteJavaScriptWithFormat(
-                  @"document.getElementById('user').value == '%@'", password));
+  EXPECT_NSEQ(@YES, ExecuteJavaScriptWithFormat(
+                        @"document.getElementById('ps2').value == '%@'", @""));
+  EXPECT_NSEQ(@YES, ExecuteJavaScriptWithFormat(
+                        @"document.getElementById('user').value == '%@'", @""));
 }
 
 // Check that unknown or null identifiers are handled gracefully.
diff --git a/ios/chrome/browser/signin/authentication_service.h b/ios/chrome/browser/signin/authentication_service.h
index 7b2a7c649..82d0c0d 100644
--- a/ios/chrome/browser/signin/authentication_service.h
+++ b/ios/chrome/browser/signin/authentication_service.h
@@ -14,31 +14,25 @@
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/signin/core/browser/signin_metrics.h"
-#include "google_apis/gaia/oauth2_token_service.h"
 #include "ios/public/provider/chrome/browser/signin/chrome_identity_service.h"
+#include "services/identity/public/cpp/identity_manager.h"
 
 namespace syncer {
 class SyncService;
 }
 
-namespace identity {
-class IdentityManager;
-}
-
 class AuthenticationServiceDelegate;
 @class ChromeIdentity;
 class PrefService;
-class ProfileOAuth2TokenService;
 class SyncSetupService;
 
 // AuthenticationService is the Chrome interface to the iOS shared
 // authentication library.
 class AuthenticationService : public KeyedService,
-                              public OAuth2TokenService::Observer,
+                              public identity::IdentityManager::Observer,
                               public ios::ChromeIdentityService::Observer {
  public:
   AuthenticationService(PrefService* pref_service,
-                        ProfileOAuth2TokenService* token_service,
                         SyncSetupService* sync_setup_service,
                         identity::IdentityManager* identity_manager,
                         syncer::SyncService* sync_service);
@@ -180,8 +174,8 @@
   // they were stored in the  browser state prefs.
   void ComputeHaveAccountsChanged();
 
-  // OAuth2TokenService::Observer implementation.
-  void OnEndBatchChanges() override;
+  // identity::IdentityManager::Observer implementation.
+  void OnEndBatchOfRefreshTokenStateChanges() override;
 
   // ChromeIdentityServiceObserver implementation.
   void OnIdentityListChanged() override;
@@ -196,7 +190,6 @@
 
   // Pointer to the KeyedServices used by AuthenticationService.
   PrefService* pref_service_ = nullptr;
-  ProfileOAuth2TokenService* token_service_ = nullptr;
   SyncSetupService* sync_setup_service_ = nullptr;
   identity::IdentityManager* identity_manager_ = nullptr;
   syncer::SyncService* sync_service_ = nullptr;
diff --git a/ios/chrome/browser/signin/authentication_service.mm b/ios/chrome/browser/signin/authentication_service.mm
index 9df38f4..c422b02 100644
--- a/ios/chrome/browser/signin/authentication_service.mm
+++ b/ios/chrome/browser/signin/authentication_service.mm
@@ -16,8 +16,6 @@
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/pref_service.h"
 #include "components/signin/core/browser/account_info.h"
-#include "components/signin/core/browser/profile_oauth2_token_service.h"
-#include "components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.h"
 #include "components/sync/driver/sync_service.h"
 #include "components/sync/driver/sync_user_settings.h"
 #include "google_apis/gaia/gaia_auth_util.h"
@@ -31,7 +29,6 @@
 #include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
 #import "ios/public/provider/chrome/browser/signin/chrome_identity.h"
 #include "ios/public/provider/chrome/browser/signin/chrome_identity_service.h"
-#import "services/identity/public/cpp/identity_manager.h"
 #import "services/identity/public/cpp/primary_account_mutator.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -74,12 +71,10 @@
 
 AuthenticationService::AuthenticationService(
     PrefService* pref_service,
-    ProfileOAuth2TokenService* token_service,
     SyncSetupService* sync_setup_service,
     identity::IdentityManager* identity_manager,
     syncer::SyncService* sync_service)
     : pref_service_(pref_service),
-      token_service_(token_service),
       sync_setup_service_(sync_setup_service),
       identity_manager_(identity_manager),
       sync_service_(sync_service),
@@ -89,7 +84,7 @@
   DCHECK(sync_setup_service_);
   DCHECK(identity_manager_);
   DCHECK(sync_service_);
-  token_service_->AddObserver(this);
+  identity_manager_->AddObserver(this);
 }
 
 AuthenticationService::~AuthenticationService() {
@@ -151,7 +146,7 @@
 }
 
 void AuthenticationService::Shutdown() {
-  token_service_->RemoveObserver(this);
+  identity_manager_->RemoveObserver(this);
 
   NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
   [center removeObserver:foreground_observer_];
@@ -196,13 +191,11 @@
 
   // Clear signin errors on the accounts that had a specific MDM device status.
   // This will trigger services to fetch data for these accounts again.
-  ProfileOAuth2TokenServiceIOSDelegate* token_service_delegate =
-      static_cast<ProfileOAuth2TokenServiceIOSDelegate*>(
-          token_service_->GetDelegate());
   std::map<std::string, NSDictionary*> cached_mdm_infos(cached_mdm_infos_);
   cached_mdm_infos_.clear();
   for (const auto& cached_mdm_info : cached_mdm_infos) {
-    token_service_delegate->AddOrUpdateAccount(cached_mdm_info.first);
+    // TODO(crbug.com/930094): Eliminate this.
+    identity_manager_->LegacyAddAccountFromSystem(cached_mdm_info.first);
   }
 }
 
@@ -358,10 +351,8 @@
 
   // Reload all credentials to match the desktop model. Exclude all the
   // accounts ids that are the primary account ids on other profiles.
-  ProfileOAuth2TokenServiceIOSDelegate* tokenServiceDelegate =
-      static_cast<ProfileOAuth2TokenServiceIOSDelegate*>(
-          token_service_->GetDelegate());
-  tokenServiceDelegate->ReloadCredentials(new_authenticated_account_id);
+  // TODO(crbug.com/930094): Eliminate this.
+  identity_manager_->LegacyReloadAccountsFromSystem();
   StoreAccountsInPrefs();
 
   // Kick-off sync: The authentication error UI (sign in infobar and warning
@@ -413,7 +404,8 @@
     return nil;
   }
 
-  if (!token_service_->RefreshTokenHasError(it->first)) {
+  if (!identity_manager_->HasAccountWithRefreshTokenInPersistentErrorState(
+          it->first)) {
     // Account has no error, invalidate the cache.
     cached_mdm_infos_.erase(it);
     return nil;
@@ -451,7 +443,7 @@
   return weak_pointer_factory_.GetWeakPtr();
 }
 
-void AuthenticationService::OnEndBatchChanges() {
+void AuthenticationService::OnEndBatchOfRefreshTokenStateChanges() {
   if (is_in_foreground_) {
     // Accounts maybe have been excluded or included from the current browser
     // state, without any change to the identity list.
@@ -580,10 +572,8 @@
 
   HandleForgottenIdentity(nil, should_prompt);
   if (GetAuthenticatedUserEmail()) {
-    ProfileOAuth2TokenServiceIOSDelegate* token_service_delegate =
-        static_cast<ProfileOAuth2TokenServiceIOSDelegate*>(
-            token_service_->GetDelegate());
-    token_service_delegate->ReloadCredentials();
+    // TODO(crbug.com/930094): Eliminate this.
+    identity_manager_->LegacyReloadAccountsFromSystem();
   }
 }
 
diff --git a/ios/chrome/browser/signin/authentication_service_factory.mm b/ios/chrome/browser/signin/authentication_service_factory.mm
index 810d24e5..a436d2b 100644
--- a/ios/chrome/browser/signin/authentication_service_factory.mm
+++ b/ios/chrome/browser/signin/authentication_service_factory.mm
@@ -13,7 +13,6 @@
 #import "ios/chrome/browser/signin/authentication_service.h"
 #import "ios/chrome/browser/signin/authentication_service_delegate.h"
 #include "ios/chrome/browser/signin/identity_manager_factory.h"
-#include "ios/chrome/browser/signin/profile_oauth2_token_service_factory.h"
 #include "ios/chrome/browser/sync/profile_sync_service_factory.h"
 #include "ios/chrome/browser/sync/sync_setup_service_factory.h"
 
@@ -50,7 +49,6 @@
     : BrowserStateKeyedServiceFactory(
           "AuthenticationService",
           BrowserStateDependencyManager::GetInstance()) {
-  DependsOn(ProfileOAuth2TokenServiceFactory::GetInstance());
   DependsOn(IdentityManagerFactory::GetInstance());
   DependsOn(SyncSetupServiceFactory::GetInstance());
   DependsOn(ProfileSyncServiceFactory::GetInstance());
@@ -65,7 +63,6 @@
       ios::ChromeBrowserState::FromBrowserState(context);
   return std::make_unique<AuthenticationService>(
       browser_state->GetPrefs(),
-      ProfileOAuth2TokenServiceFactory::GetForBrowserState(browser_state),
       SyncSetupServiceFactory::GetForBrowserState(browser_state),
       IdentityManagerFactory::GetForBrowserState(browser_state),
       ProfileSyncServiceFactory::GetForBrowserState(browser_state));
diff --git a/ios/chrome/browser/signin/authentication_service_fake.h b/ios/chrome/browser/signin/authentication_service_fake.h
index 3ae16b5..8cbdc80 100644
--- a/ios/chrome/browser/signin/authentication_service_fake.h
+++ b/ios/chrome/browser/signin/authentication_service_fake.h
@@ -44,7 +44,6 @@
 
  private:
   AuthenticationServiceFake(PrefService* pref_service,
-                            ProfileOAuth2TokenService* token_service,
                             SyncSetupService* sync_setup_service,
                             identity::IdentityManager* identity_manager,
                             syncer::SyncService* sync_service);
diff --git a/ios/chrome/browser/signin/authentication_service_fake.mm b/ios/chrome/browser/signin/authentication_service_fake.mm
index 8dd076395..604bbc4 100644
--- a/ios/chrome/browser/signin/authentication_service_fake.mm
+++ b/ios/chrome/browser/signin/authentication_service_fake.mm
@@ -11,7 +11,6 @@
 #import "ios/chrome/browser/signin/authentication_service_delegate_fake.h"
 #import "ios/chrome/browser/signin/authentication_service_factory.h"
 #include "ios/chrome/browser/signin/identity_manager_factory.h"
-#include "ios/chrome/browser/signin/profile_oauth2_token_service_factory.h"
 #include "ios/chrome/browser/sync/profile_sync_service_factory.h"
 #include "ios/chrome/browser/sync/sync_setup_service_factory.h"
 #import "ios/public/provider/chrome/browser/signin/chrome_identity.h"
@@ -22,12 +21,10 @@
 
 AuthenticationServiceFake::AuthenticationServiceFake(
     PrefService* pref_service,
-    ProfileOAuth2TokenService* token_service,
     SyncSetupService* sync_setup_service,
     identity::IdentityManager* identity_manager,
     syncer::SyncService* sync_service)
     : AuthenticationService(pref_service,
-                            token_service,
                             sync_setup_service,
                             identity_manager,
                             sync_service),
@@ -75,7 +72,6 @@
       ios::ChromeBrowserState::FromBrowserState(context);
   auto service = base::WrapUnique(new AuthenticationServiceFake(
       browser_state->GetPrefs(),
-      ProfileOAuth2TokenServiceFactory::GetForBrowserState(browser_state),
       SyncSetupServiceFactory::GetForBrowserState(browser_state),
       IdentityManagerFactory::GetForBrowserState(browser_state),
       ProfileSyncServiceFactory::GetForBrowserState(browser_state)));
diff --git a/ios/chrome/browser/signin/authentication_service_unittest.mm b/ios/chrome/browser/signin/authentication_service_unittest.mm
index bf43a4d..501b0b8 100644
--- a/ios/chrome/browser/signin/authentication_service_unittest.mm
+++ b/ios/chrome/browser/signin/authentication_service_unittest.mm
@@ -10,9 +10,7 @@
 #include "components/keyed_service/core/service_access_type.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/pref_registry_simple.h"
-#include "components/signin/core/browser/profile_oauth2_token_service.h"
 #include "components/signin/core/browser/signin_pref_names.h"
-#include "components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.h"
 #include "components/sync_preferences/pref_service_mock_factory.h"
 #include "components/sync_preferences/pref_service_syncable.h"
 #include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
@@ -28,7 +26,6 @@
 #import "ios/chrome/browser/signin/identity_manager_factory.h"
 #import "ios/chrome/browser/signin/identity_test_environment_chrome_browser_state_adaptor.h"
 #include "ios/chrome/browser/signin/ios_chrome_signin_client.h"
-#include "ios/chrome/browser/signin/profile_oauth2_token_service_factory.h"
 #include "ios/chrome/browser/signin/signin_client_factory.h"
 #include "ios/chrome/browser/sync/ios_chrome_profile_sync_test_util.h"
 #include "ios/chrome/browser/sync/profile_sync_service_factory.h"
@@ -175,8 +172,6 @@
     }
     authentication_service_ = std::make_unique<AuthenticationService>(
         browser_state_->GetPrefs(),
-        ProfileOAuth2TokenServiceFactory::GetForBrowserState(
-            browser_state_.get()),
         SyncSetupServiceFactory::GetForBrowserState(browser_state_.get()),
         IdentityManagerFactory::GetForBrowserState(browser_state_.get()),
         ProfileSyncServiceFactory::GetForBrowserState(browser_state_.get()));
@@ -235,8 +230,11 @@
   }
 
   identity::IdentityManager* identity_manager() {
-    return identity_test_environment_adaptor_->identity_test_env()
-        ->identity_manager();
+    return identity_test_env()->identity_manager();
+  }
+
+  identity::IdentityTestEnvironment* identity_test_env() {
+    return identity_test_environment_adaptor_->identity_test_env();
   }
 
   web::TestWebThreadBundle thread_bundle_;
@@ -264,10 +262,6 @@
 
   EXPECT_NSEQ(identity_, authentication_service_->GetAuthenticatedIdentity());
 
-  ProfileOAuth2TokenService* token_service =
-      ProfileOAuth2TokenServiceFactory::GetForBrowserState(
-          browser_state_.get());
-
   std::string user_email = base::SysNSStringToUTF8([identity_ userEmail]);
   AccountInfo account_info =
       identity_manager()
@@ -275,7 +269,8 @@
           .value();
   EXPECT_EQ(user_email, account_info.email);
   EXPECT_EQ(base::SysNSStringToUTF8([identity_ gaiaID]), account_info.gaia);
-  EXPECT_TRUE(token_service->RefreshTokenIsAvailable(account_info.account_id));
+  EXPECT_TRUE(
+      identity_manager()->HasAccountWithRefreshToken(account_info.account_id));
 }
 
 TEST_F(AuthenticationServiceTest, TestSetPromptForSignIn) {
@@ -572,13 +567,8 @@
   // AccountTrackerService::Initialize(), it fails because account ids are
   // updated with gaia ID from email at MigrateToGaiaId. As IdentityManager
   // needs refresh token to find account info, it reloads all credentials.
-  ProfileOAuth2TokenService* token_service =
-      ProfileOAuth2TokenServiceFactory::GetForBrowserState(
-          browser_state_.get());
-  ProfileOAuth2TokenServiceIOSDelegate* token_service_delegate =
-      static_cast<ProfileOAuth2TokenServiceIOSDelegate*>(
-          token_service->GetDelegate());
-  token_service_delegate->ReloadCredentials();
+  // TODO(crbug.com/930094): Eliminate this.
+  identity_manager()->LegacyReloadAccountsFromSystem();
 
   // Actually migrate the accounts in prefs.
   MigrateAccountsStoredInPrefsIfNeeded();
@@ -605,9 +595,8 @@
   SetCachedMDMInfo(identity_, user_info);
   GoogleServiceAuthError error(
       GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
-  ProfileOAuth2TokenServiceFactory::GetForBrowserState(browser_state_.get())
-      ->GetDelegate()
-      ->UpdateAuthError(base::SysNSStringToUTF8([identity_ gaiaID]), error);
+  identity_test_env()->UpdatePersistentErrorOfRefreshTokenForAccount(
+      base::SysNSStringToUTF8([identity_ gaiaID]), error);
   EXPECT_EQ(2, refresh_token_available_count_);
 
   // MDM error for |identity_| is being cleared, refresh token available
@@ -646,9 +635,8 @@
   authentication_service_->SignIn(identity_, std::string());
   GoogleServiceAuthError error(
       GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
-  ProfileOAuth2TokenServiceFactory::GetForBrowserState(browser_state_.get())
-      ->GetDelegate()
-      ->UpdateAuthError(base::SysNSStringToUTF8([identity_ gaiaID]), error);
+  identity_test_env()->UpdatePersistentErrorOfRefreshTokenForAccount(
+      base::SysNSStringToUTF8([identity_ gaiaID]), error);
 
   NSDictionary* user_info1 = @{ @"foo" : @1 };
   ON_CALL(*identity_service_, GetMDMDeviceStatus(user_info1))
@@ -683,9 +671,8 @@
   authentication_service_->SignIn(identity_, std::string());
   GoogleServiceAuthError error(
       GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
-  ProfileOAuth2TokenServiceFactory::GetForBrowserState(browser_state_.get())
-      ->GetDelegate()
-      ->UpdateAuthError(base::SysNSStringToUTF8([identity_ gaiaID]), error);
+  identity_test_env()->UpdatePersistentErrorOfRefreshTokenForAccount(
+      base::SysNSStringToUTF8([identity_ gaiaID]), error);
 
   NSDictionary* user_info1 = @{ @"foo" : @1 };
   ON_CALL(*identity_service_, GetMDMDeviceStatus(user_info1))
@@ -742,9 +729,8 @@
   authentication_service_->SignIn(identity_, std::string());
   GoogleServiceAuthError error(
       GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
-  ProfileOAuth2TokenServiceFactory::GetForBrowserState(browser_state_.get())
-      ->GetDelegate()
-      ->UpdateAuthError(base::SysNSStringToUTF8([identity_ gaiaID]), error);
+  identity_test_env()->UpdatePersistentErrorOfRefreshTokenForAccount(
+      base::SysNSStringToUTF8([identity_ gaiaID]), error);
 
   NSDictionary* user_info = [NSDictionary dictionary];
   SetCachedMDMInfo(identity_, user_info);
diff --git a/ios/chrome/browser/ui/authentication/cells/account_control_item.h b/ios/chrome/browser/ui/authentication/cells/account_control_item.h
index b5e71d7..2ec7c75 100644
--- a/ios/chrome/browser/ui/authentication/cells/account_control_item.h
+++ b/ios/chrome/browser/ui/authentication/cells/account_control_item.h
@@ -22,7 +22,7 @@
 
 // Cell for account settings view with a leading imageView, title text label,
 // and detail text label. The imageView is top-leading aligned.
-@interface AccountControlCell : UITableViewCell
+@interface AccountControlCell : TableViewCell
 
 @property(nonatomic, readonly, strong) UIImageView* imageView;
 @property(nonatomic, readonly, strong) UILabel* textLabel;
diff --git a/ios/chrome/browser/ui/authentication/cells/table_view_account_item.h b/ios/chrome/browser/ui/authentication/cells/table_view_account_item.h
index 2bddb603..12fee1f 100644
--- a/ios/chrome/browser/ui/authentication/cells/table_view_account_item.h
+++ b/ios/chrome/browser/ui/authentication/cells/table_view_account_item.h
@@ -29,7 +29,7 @@
 // imageView. The imageView is vertical-centered and leading aligned.
 // If item/cell is disabled the image and text alpha will be set to 0.5 and
 // user interaction will be disabled.
-@interface TableViewAccountCell : UITableViewCell
+@interface TableViewAccountCell : TableViewCell
 
 // Rounded image used for the account user picture.
 @property(nonatomic, readonly, strong) UIImageView* imageView;
diff --git a/ios/chrome/browser/ui/authentication/cells/table_view_signin_promo_item.h b/ios/chrome/browser/ui/authentication/cells/table_view_signin_promo_item.h
index d256e77..4fa8f95 100644
--- a/ios/chrome/browser/ui/authentication/cells/table_view_signin_promo_item.h
+++ b/ios/chrome/browser/ui/authentication/cells/table_view_signin_promo_item.h
@@ -23,8 +23,8 @@
 @property(nonatomic, readwrite, strong) NSString* text;
 @end
 
-// UITableViewCell that contains a SignInPromoView.
-@interface TableViewSigninPromoCell : UITableViewCell
+// TableViewCell that contains a SignInPromoView.
+@interface TableViewSigninPromoCell : TableViewCell
 // The SigninPromoView contained by this Cell.
 @property(nonatomic, strong) SigninPromoView* signinPromoView;
 @end
diff --git a/ios/chrome/browser/ui/authentication/cells/table_view_signin_promo_item.mm b/ios/chrome/browser/ui/authentication/cells/table_view_signin_promo_item.mm
index e6f2e2f..39723f5 100644
--- a/ios/chrome/browser/ui/authentication/cells/table_view_signin_promo_item.mm
+++ b/ios/chrome/browser/ui/authentication/cells/table_view_signin_promo_item.mm
@@ -30,7 +30,7 @@
   return self;
 }
 
-- (void)configureCell:(UITableViewCell*)tableCell
+- (void)configureCell:(TableViewCell*)tableCell
            withStyler:(ChromeTableViewStyler*)styler {
   [super configureCell:tableCell withStyler:styler];
   TableViewSigninPromoCell* cell =
diff --git a/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/identity_chooser_cell.h b/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/identity_chooser_cell.h
index 74520db..65d3105 100644
--- a/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/identity_chooser_cell.h
+++ b/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/identity_chooser_cell.h
@@ -7,10 +7,12 @@
 
 #import <UIKit/UIKit.h>
 
+#import "ios/chrome/browser/ui/table_view/cells/table_view_cell.h"
+
 @class IdentityView;
 
 // Cell to display an user identity or the "Add Account…" button.
-@interface IdentityChooserCell : UITableViewCell
+@interface IdentityChooserCell : TableViewCell
 
 // Initializes IdentityChooserCell instance.
 - (instancetype)initWithStyle:(UITableViewCellStyle)style
diff --git a/ios/chrome/browser/ui/autofill/cells/autofill_edit_item.h b/ios/chrome/browser/ui/autofill/cells/autofill_edit_item.h
index fc2d726..5e175cb 100644
--- a/ios/chrome/browser/ui/autofill/cells/autofill_edit_item.h
+++ b/ios/chrome/browser/ui/autofill/cells/autofill_edit_item.h
@@ -46,9 +46,9 @@
 
 @end
 
-// AutofillEditCell implements an UITableViewCell subclass containing a label
+// AutofillEditCell implements an TableViewCell subclass containing a label
 // and a text field.
-@interface AutofillEditCell : UITableViewCell
+@interface AutofillEditCell : TableViewCell
 
 // Label at the leading edge of the cell. It displays the item's textFieldName.
 @property(nonatomic, strong) UILabel* textLabel;
diff --git a/ios/chrome/browser/ui/autofill/cells/autofill_edit_item.mm b/ios/chrome/browser/ui/autofill/cells/autofill_edit_item.mm
index 5e9595e..d16f5c3 100644
--- a/ios/chrome/browser/ui/autofill/cells/autofill_edit_item.mm
+++ b/ios/chrome/browser/ui/autofill/cells/autofill_edit_item.mm
@@ -274,7 +274,7 @@
   }
 }
 
-#pragma mark UITableViewCell
+#pragma mark - UITableViewCell
 
 - (void)prepareForReuse {
   [super prepareForReuse];
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/action_cell.h b/ios/chrome/browser/ui/autofill/manual_fill/action_cell.h
index a3b26f4..3c30a59f 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/action_cell.h
+++ b/ios/chrome/browser/ui/autofill/manual_fill/action_cell.h
@@ -27,7 +27,7 @@
 
 // A table view cell which contains a button and holds an action block, which
 // is called when the button is touched.
-@interface ManualFillActionCell : UITableViewCell
+@interface ManualFillActionCell : TableViewCell
 // Updates the cell with the passed title and action block.
 - (void)setUpWithTitle:(NSString*)title
        accessibilityID:(NSString*)accessibilityID
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_address_cell.h b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_address_cell.h
index 1b6c4b1..ed81d4c 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_address_cell.h
+++ b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_address_cell.h
@@ -28,7 +28,7 @@
 
 // Cell to display an Address into parts that are interactable
 // and sendable the data to the delegate.
-@interface ManualFillAddressCell : UITableViewCell
+@interface ManualFillAddressCell : TableViewCell
 
 // Updates the cell with address and the |delegate| to be notified.
 - (void)setUpWithAddress:(ManualFillAddress*)profile
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_card_cell.h b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_card_cell.h
index 99d6a1d..74712039 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_card_cell.h
+++ b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_card_cell.h
@@ -28,7 +28,7 @@
 
 // Cell to display a Card where the username and password are interactable
 // and send the data to the delegate.
-@interface ManualFillCardCell : UITableViewCell
+@interface ManualFillCardCell : TableViewCell
 
 // Updates the cell with credit card and the |delegate| to be notified.
 - (void)setUpWithCreditCard:(ManualFillCreditCard*)card
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_cell.h b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_cell.h
index 5ecea917..cb6c71a1 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_cell.h
+++ b/ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_cell.h
@@ -27,7 +27,7 @@
 
 // Cell to display a Credential where the username and password are interactable
 // and send the data to the delegate.
-@interface ManualFillPasswordCell : UITableViewCell
+@interface ManualFillPasswordCell : TableViewCell
 
 // Updates the cell with the |credential|. If the user iteracts with it, the
 // |delegate| will be notified.
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_home_view_controller.mm b/ios/chrome/browser/ui/bookmarks/bookmark_home_view_controller.mm
index 6096562..150f8998 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmark_home_view_controller.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_home_view_controller.mm
@@ -1302,6 +1302,10 @@
   [self setupContextBar];
 }
 
+- (BOOL)scrimIsVisible {
+  return self.scrimView.superview ? YES : NO;
+}
+
 #pragma mark - Loading and Empty States
 
 // Shows loading spinner background view.
@@ -1693,8 +1697,8 @@
 
 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
        shouldReceiveTouch:(UITouch*)touch {
-  // Ignore long press in edit mode.
-  if (self.sharedState.currentlyInEditMode) {
+  // Ignore long press in edit mode or search mode.
+  if (self.sharedState.currentlyInEditMode || [self scrimIsVisible]) {
     return NO;
   }
   return YES;
diff --git a/ios/chrome/browser/ui/bookmarks/bookmarks_egtest.mm b/ios/chrome/browser/ui/bookmarks/bookmarks_egtest.mm
index 2eb93ff4..0cbcdcb 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmarks_egtest.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmarks_egtest.mm
@@ -4236,6 +4236,43 @@
       assertWithMatcher:grey_notNil()];
 }
 
+// Tests that long press on scrim while search box is enabled dismisses the
+// search controller.
+- (void)testSearchLongPressOnScrimCancelsSearchController {
+  [BookmarksTestCase setupStandardBookmarks];
+  [BookmarksTestCase openBookmarks];
+  [BookmarksTestCase openMobileBookmarks];
+
+  [[EarlGrey selectElementWithMatcher:SearchIconButton()]
+      performAction:grey_tap()];
+
+  // Try long press.
+  [[EarlGrey
+      selectElementWithMatcher:TappableBookmarkNodeWithLabel(@"First URL")]
+      performAction:grey_longPress()];
+
+  // Verify context menu is not visible.
+  [[EarlGrey selectElementWithMatcher:ButtonWithAccessibilityLabelId(
+                                          IDS_IOS_BOOKMARK_CONTEXT_MENU_EDIT)]
+      assertWithMatcher:grey_nil()];
+
+  // Verify that scrim is not visible.
+  [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
+                                          kBookmarkHomeSearchScrimIdentifier)]
+      assertWithMatcher:grey_nil()];
+
+  // Verifiy we went back to original folder content.
+  [[EarlGrey
+      selectElementWithMatcher:TappableBookmarkNodeWithLabel(@"First URL")]
+      assertWithMatcher:grey_notNil()];
+  [[EarlGrey
+      selectElementWithMatcher:TappableBookmarkNodeWithLabel(@"Second URL")]
+      assertWithMatcher:grey_notNil()];
+  [[EarlGrey
+      selectElementWithMatcher:TappableBookmarkNodeWithLabel(@"French URL")]
+      assertWithMatcher:grey_notNil()];
+}
+
 // Tests cancelling search restores the node's bookmarks.
 - (void)testSearchCancelRestoresNodeBookmarks {
   [BookmarksTestCase setupStandardBookmarks];
diff --git a/ios/chrome/browser/ui/bookmarks/cells/bookmark_folder_item.h b/ios/chrome/browser/ui/bookmarks/cells/bookmark_folder_item.h
index d4315026..84894f2 100644
--- a/ios/chrome/browser/ui/bookmarks/cells/bookmark_folder_item.h
+++ b/ios/chrome/browser/ui/bookmarks/cells/bookmark_folder_item.h
@@ -43,7 +43,7 @@
 
 // TableViewCell that displays BookmarkFolderItem data.
 @interface TableViewBookmarkFolderCell
-    : UITableViewCell<BookmarkTableCellTitleEditing>
+    : TableViewCell <BookmarkTableCellTitleEditing>
 
 // The leading constraint used to set the cell's leading indentation. The
 // default indentationLevel property doesn't affect any custom Cell subviews,
diff --git a/ios/chrome/browser/ui/bookmarks/cells/bookmark_folder_item.mm b/ios/chrome/browser/ui/bookmarks/cells/bookmark_folder_item.mm
index 7d255d2..83cca1d 100644
--- a/ios/chrome/browser/ui/bookmarks/cells/bookmark_folder_item.mm
+++ b/ios/chrome/browser/ui/bookmarks/cells/bookmark_folder_item.mm
@@ -50,7 +50,7 @@
   return self;
 }
 
-- (void)configureCell:(UITableViewCell*)cell
+- (void)configureCell:(TableViewCell*)cell
            withStyler:(ChromeTableViewStyler*)styler {
   [super configureCell:cell withStyler:styler];
   TableViewBookmarkFolderCell* folderCell =
diff --git a/ios/chrome/browser/ui/bookmarks/cells/bookmark_home_node_item.mm b/ios/chrome/browser/ui/bookmarks/cells/bookmark_home_node_item.mm
index 6fbb9994..a928daf 100644
--- a/ios/chrome/browser/ui/bookmarks/cells/bookmark_home_node_item.mm
+++ b/ios/chrome/browser/ui/bookmarks/cells/bookmark_home_node_item.mm
@@ -31,7 +31,7 @@
   return self;
 }
 
-- (void)configureCell:(UITableViewCell*)cell
+- (void)configureCell:(TableViewCell*)cell
            withStyler:(ChromeTableViewStyler*)styler {
   [super configureCell:cell withStyler:styler];
   if (_bookmarkNode->is_folder()) {
diff --git a/ios/chrome/browser/ui/bookmarks/cells/bookmark_home_promo_item.mm b/ios/chrome/browser/ui/bookmarks/cells/bookmark_home_promo_item.mm
index 74d2552..77b8c02 100644
--- a/ios/chrome/browser/ui/bookmarks/cells/bookmark_home_promo_item.mm
+++ b/ios/chrome/browser/ui/bookmarks/cells/bookmark_home_promo_item.mm
@@ -28,7 +28,7 @@
   return self;
 }
 
-- (void)configureCell:(UITableViewCell*)cell
+- (void)configureCell:(TableViewCell*)cell
            withStyler:(ChromeTableViewStyler*)styler {
   [super configureCell:cell withStyler:styler];
   BookmarkTableSigninPromoCell* signinPromoCell =
diff --git a/ios/chrome/browser/ui/bookmarks/cells/bookmark_parent_folder_item.h b/ios/chrome/browser/ui/bookmarks/cells/bookmark_parent_folder_item.h
index 2911634..25a7e01 100644
--- a/ios/chrome/browser/ui/bookmarks/cells/bookmark_parent_folder_item.h
+++ b/ios/chrome/browser/ui/bookmarks/cells/bookmark_parent_folder_item.h
@@ -18,7 +18,7 @@
 @end
 
 // Cell class associated to BookmarkParentFolderItem.
-@interface BookmarkParentFolderCell : UITableViewCell
+@interface BookmarkParentFolderCell : TableViewCell
 
 // Label that displays the item's title.
 @property(nonatomic, readonly, strong) UILabel* parentFolderNameLabel;
diff --git a/ios/chrome/browser/ui/bookmarks/cells/bookmark_parent_folder_item.mm b/ios/chrome/browser/ui/bookmarks/cells/bookmark_parent_folder_item.mm
index e57810b..1a211e0 100644
--- a/ios/chrome/browser/ui/bookmarks/cells/bookmark_parent_folder_item.mm
+++ b/ios/chrome/browser/ui/bookmarks/cells/bookmark_parent_folder_item.mm
@@ -36,7 +36,7 @@
 
 #pragma mark TableViewItem
 
-- (void)configureCell:(UITableViewCell*)tableCell
+- (void)configureCell:(TableViewCell*)tableCell
            withStyler:(ChromeTableViewStyler*)styler {
   [super configureCell:tableCell withStyler:styler];
   BookmarkParentFolderCell* cell =
diff --git a/ios/chrome/browser/ui/bookmarks/cells/bookmark_table_signin_promo_cell.h b/ios/chrome/browser/ui/bookmarks/cells/bookmark_table_signin_promo_cell.h
index 0492503a..500a3bd 100644
--- a/ios/chrome/browser/ui/bookmarks/cells/bookmark_table_signin_promo_cell.h
+++ b/ios/chrome/browser/ui/bookmarks/cells/bookmark_table_signin_promo_cell.h
@@ -5,13 +5,15 @@
 #ifndef IOS_CHROME_BROWSER_UI_BOOKMARKS_CELLS_BOOKMARK_TABLE_SIGNIN_PROMO_CELL_H_
 #define IOS_CHROME_BROWSER_UI_BOOKMARKS_CELLS_BOOKMARK_TABLE_SIGNIN_PROMO_CELL_H_
 
-#import <UIKit/UIKit.h>
+#import <Foundation/Foundation.h>
+
+#import "ios/chrome/browser/ui/table_view/cells/table_view_cell.h"
 
 @class SigninPromoView;
 
 // Sign-in promo cell based on SigninPromoView. This cell invites the user to
 // login without typing their password.
-@interface BookmarkTableSigninPromoCell : UITableViewCell
+@interface BookmarkTableSigninPromoCell : TableViewCell
 
 // Identifier for -[UITableView registerClass:forCellWithReuseIdentifier:].
 + (NSString*)reuseIdentifier;
diff --git a/ios/chrome/browser/ui/bookmarks/cells/bookmark_text_field_item.h b/ios/chrome/browser/ui/bookmarks/cells/bookmark_text_field_item.h
index 00feaee..87cae9ad 100644
--- a/ios/chrome/browser/ui/bookmarks/cells/bookmark_text_field_item.h
+++ b/ios/chrome/browser/ui/bookmarks/cells/bookmark_text_field_item.h
@@ -34,7 +34,7 @@
 
 @end
 
-@interface BookmarkTextFieldCell : UITableViewCell
+@interface BookmarkTextFieldCell : TableViewCell
 
 // Label to display the type of content |self.textField| is displaying.
 @property(nonatomic, strong) UILabel* titleLabel;
diff --git a/ios/chrome/browser/ui/bookmarks/cells/bookmark_text_field_item.mm b/ios/chrome/browser/ui/bookmarks/cells/bookmark_text_field_item.mm
index 5460e8a..d90fce9 100644
--- a/ios/chrome/browser/ui/bookmarks/cells/bookmark_text_field_item.mm
+++ b/ios/chrome/browser/ui/bookmarks/cells/bookmark_text_field_item.mm
@@ -35,7 +35,7 @@
 
 #pragma mark TableViewItem
 
-- (void)configureCell:(UITableViewCell*)tableCell
+- (void)configureCell:(TableViewCell*)tableCell
            withStyler:(ChromeTableViewStyler*)styler {
   [super configureCell:tableCell withStyler:styler];
 
diff --git a/ios/chrome/browser/ui/history/history_entries_status_item.h b/ios/chrome/browser/ui/history/history_entries_status_item.h
index f98f0ab6..b4e8ae9 100644
--- a/ios/chrome/browser/ui/history/history_entries_status_item.h
+++ b/ios/chrome/browser/ui/history/history_entries_status_item.h
@@ -13,7 +13,7 @@
 @end
 
 // Cell that displays a HistoryEntriesStatusItem.
-@interface HistoryEntriesStatusCell : UITableViewCell
+@interface HistoryEntriesStatusCell : TableViewCell
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_HISTORY_HISTORY_ENTRIES_STATUS_ITEM_H_
diff --git a/ios/chrome/browser/ui/history/history_entry_item.mm b/ios/chrome/browser/ui/history/history_entry_item.mm
index 2cfb06e..bc3d7ac 100644
--- a/ios/chrome/browser/ui/history/history_entry_item.mm
+++ b/ios/chrome/browser/ui/history/history_entry_item.mm
@@ -46,7 +46,7 @@
   return self;
 }
 
-- (void)configureCell:(UITableViewCell*)tableCell
+- (void)configureCell:(TableViewCell*)tableCell
            withStyler:(ChromeTableViewStyler*)styler {
   [super configureCell:tableCell withStyler:styler];
 
diff --git a/ios/chrome/browser/ui/history/history_table_view_controller.mm b/ios/chrome/browser/ui/history/history_table_view_controller.mm
index 4e4e891f..3a7b187 100644
--- a/ios/chrome/browser/ui/history/history_table_view_controller.mm
+++ b/ios/chrome/browser/ui/history/history_table_view_controller.mm
@@ -918,6 +918,10 @@
   }
 }
 
+- (BOOL)scrimIsVisible {
+  return self.scrimView.superview ? YES : NO;
+}
+
 #pragma mark Navigation Toolbar Configuration
 
 // Animates the view configuration after flipping the current status of |[self
@@ -977,6 +981,10 @@
       gestureRecognizer.state != UIGestureRecognizerStateBegan) {
     return;
   }
+  if ([self scrimIsVisible]) {
+    self.searchController.active = NO;
+    return;
+  }
 
   CGPoint touchLocation =
       [gestureRecognizer locationOfTouch:0 inView:self.tableView];
diff --git a/ios/chrome/browser/ui/history/history_ui_egtest.mm b/ios/chrome/browser/ui/history/history_ui_egtest.mm
index 2a514023..5d9e900 100644
--- a/ios/chrome/browser/ui/history/history_ui_egtest.mm
+++ b/ios/chrome/browser/ui/history/history_ui_egtest.mm
@@ -253,6 +253,38 @@
       assertWithMatcher:grey_nil()];
 }
 
+// Tests that long press on scrim while search box is enabled dismisses the
+// search controller.
+- (void)testSearchLongPressOnScrimCancelsSearchController {
+  [self loadTestURLs];
+  [self openHistoryPanel];
+  [[EarlGrey selectElementWithMatcher:SearchIconButton()]
+      performAction:grey_tap()];
+
+  // Try long press.
+  [[EarlGrey selectElementWithMatcher:HistoryEntry(_URL1, kTitle1)]
+      performAction:grey_longPress()];
+
+  // Verify context menu is not visible.
+  [[EarlGrey
+      selectElementWithMatcher:ButtonWithAccessibilityLabelId(
+                                   IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB)]
+      assertWithMatcher:grey_nil()];
+
+  // Verify that scrim is not visible.
+  [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
+                                          kHistorySearchScrimIdentifier)]
+      assertWithMatcher:grey_nil()];
+
+  // Verifiy we went back to original folder content.
+  [[EarlGrey selectElementWithMatcher:HistoryEntry(_URL1, kTitle1)]
+      assertWithMatcher:grey_notNil()];
+  [[EarlGrey selectElementWithMatcher:HistoryEntry(_URL2, kTitle2)]
+      assertWithMatcher:grey_notNil()];
+  [[EarlGrey selectElementWithMatcher:HistoryEntry(_URL3, _URL3.GetContent())]
+      assertWithMatcher:grey_notNil()];
+}
+
 // Tests deletion of history entries.
 - (void)testDeleteHistory {
   [self loadTestURLs];
diff --git a/ios/chrome/browser/ui/popup_menu/cells/popup_menu_navigation_item.h b/ios/chrome/browser/ui/popup_menu/cells/popup_menu_navigation_item.h
index 6fd7a7b..539ea9bb 100644
--- a/ios/chrome/browser/ui/popup_menu/cells/popup_menu_navigation_item.h
+++ b/ios/chrome/browser/ui/popup_menu/cells/popup_menu_navigation_item.h
@@ -23,7 +23,7 @@
 @end
 
 // Associated cell for a PopupMenuNavigationItem.
-@interface PopupMenuNavigationCell : UITableViewCell
+@interface PopupMenuNavigationCell : TableViewCell
 
 - (void)setTitle:(NSString*)title;
 - (void)setFavicon:(UIImage*)favicon;
diff --git a/ios/chrome/browser/ui/popup_menu/cells/popup_menu_tools_item.h b/ios/chrome/browser/ui/popup_menu/cells/popup_menu_tools_item.h
index 8be2b5d..af1646f0 100644
--- a/ios/chrome/browser/ui/popup_menu/cells/popup_menu_tools_item.h
+++ b/ios/chrome/browser/ui/popup_menu/cells/popup_menu_tools_item.h
@@ -29,7 +29,7 @@
 @end
 
 // Associated cell for the PopupMenuToolsItem.
-@interface PopupMenuToolsCell : UITableViewCell
+@interface PopupMenuToolsCell : TableViewCell
 
 // Image view to display the image.
 @property(nonatomic, strong, readonly) UIImageView* imageView;
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_table_view_item.mm b/ios/chrome/browser/ui/reading_list/reading_list_table_view_item.mm
index 4efc53c..582e51b 100644
--- a/ios/chrome/browser/ui/reading_list/reading_list_table_view_item.mm
+++ b/ios/chrome/browser/ui/reading_list/reading_list_table_view_item.mm
@@ -81,7 +81,7 @@
 
 #pragma mark - ListItem
 
-- (void)configureCell:(UITableViewCell*)cell
+- (void)configureCell:(TableViewCell*)cell
            withStyler:(ChromeTableViewStyler*)styler {
   [super configureCell:cell withStyler:styler];
   TableViewURLCell* URLCell = base::mac::ObjCCastStrict<TableViewURLCell>(cell);
diff --git a/ios/chrome/browser/ui/settings/cells/autofill_data_item.h b/ios/chrome/browser/ui/settings/cells/autofill_data_item.h
index fd483878..d84d67ad 100644
--- a/ios/chrome/browser/ui/settings/cells/autofill_data_item.h
+++ b/ios/chrome/browser/ui/settings/cells/autofill_data_item.h
@@ -27,7 +27,7 @@
 
 // Cell for autofill data with two leading text labels and one trailing text
 // label.
-@interface AutofillDataCell : UITableViewCell
+@interface AutofillDataCell : TableViewCell
 
 @property(nonatomic, readonly, strong) UILabel* textLabel;
 @property(nonatomic, readonly, strong) UILabel* leadingDetailTextLabel;
diff --git a/ios/chrome/browser/ui/settings/cells/byo_textfield_item.h b/ios/chrome/browser/ui/settings/cells/byo_textfield_item.h
index e3261dd..0db69e3 100644
--- a/ios/chrome/browser/ui/settings/cells/byo_textfield_item.h
+++ b/ios/chrome/browser/ui/settings/cells/byo_textfield_item.h
@@ -24,7 +24,7 @@
 @end
 
 // Cell class associated to BYOTextFieldItem.
-@interface BYOTextFieldCell : UITableViewCell
+@interface BYOTextFieldCell : TableViewCell
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_SETTINGS_CELLS_BYO_TEXTFIELD_ITEM_H_
diff --git a/ios/chrome/browser/ui/settings/cells/copied_to_chrome_item.h b/ios/chrome/browser/ui/settings/cells/copied_to_chrome_item.h
index dce72b7..afefe960 100644
--- a/ios/chrome/browser/ui/settings/cells/copied_to_chrome_item.h
+++ b/ios/chrome/browser/ui/settings/cells/copied_to_chrome_item.h
@@ -15,7 +15,7 @@
 
 // A cell indicating that the credit card has been copied to Chrome. Includes a
 // button to clear the copy.
-@interface CopiedToChromeCell : UITableViewCell
+@interface CopiedToChromeCell : TableViewCell
 
 // Text label displaying the item's text.
 @property(nonatomic, readonly, strong) UILabel* textLabel;
diff --git a/ios/chrome/browser/ui/settings/cells/passphrase_error_item.h b/ios/chrome/browser/ui/settings/cells/passphrase_error_item.h
index cf1806941..b95566a 100644
--- a/ios/chrome/browser/ui/settings/cells/passphrase_error_item.h
+++ b/ios/chrome/browser/ui/settings/cells/passphrase_error_item.h
@@ -16,7 +16,7 @@
 @end
 
 // Cell class associated to PassphraseErrorItem.
-@interface PassphraseErrorCell : UITableViewCell
+@interface PassphraseErrorCell : TableViewCell
 
 // Label for the error text.
 @property(nonatomic, readonly, strong) UILabel* textLabel;
diff --git a/ios/chrome/browser/ui/settings/cells/passphrase_error_item.mm b/ios/chrome/browser/ui/settings/cells/passphrase_error_item.mm
index e164184..db9b230 100644
--- a/ios/chrome/browser/ui/settings/cells/passphrase_error_item.mm
+++ b/ios/chrome/browser/ui/settings/cells/passphrase_error_item.mm
@@ -25,7 +25,7 @@
   return self;
 }
 
-- (void)configureCell:(UITableViewCell*)cell
+- (void)configureCell:(TableViewCell*)cell
            withStyler:(ChromeTableViewStyler*)styler {
   [super configureCell:cell withStyler:styler];
   cell.textLabel.text = self.text;
diff --git a/ios/chrome/browser/ui/settings/cells/settings_detail_item.h b/ios/chrome/browser/ui/settings/cells/settings_detail_item.h
index 2318791..3d5fe5e 100644
--- a/ios/chrome/browser/ui/settings/cells/settings_detail_item.h
+++ b/ios/chrome/browser/ui/settings/cells/settings_detail_item.h
@@ -26,11 +26,11 @@
 
 @end
 
-// SettingsDetailCell implements an UITableViewCell subclass containing an
+// SettingsDetailCell implements an TableViewCell subclass containing an
 // optional leading icon and two text labels: a "main" label and a "detail"
 // label. The two labels are laid out side-by-side and fill the full width of
 // the cell. Labels are truncated as needed to fit in the cell.
-@interface SettingsDetailCell : UITableViewCell
+@interface SettingsDetailCell : TableViewCell
 
 // UILabels corresponding to |text| and |detailText| from the item.
 @property(nonatomic, readonly, strong) UILabel* textLabel;
diff --git a/ios/chrome/browser/ui/settings/cells/settings_image_detail_text_cell.h b/ios/chrome/browser/ui/settings/cells/settings_image_detail_text_cell.h
index 275ac3a..507eaa6 100644
--- a/ios/chrome/browser/ui/settings/cells/settings_image_detail_text_cell.h
+++ b/ios/chrome/browser/ui/settings/cells/settings_image_detail_text_cell.h
@@ -16,7 +16,7 @@
 //  |  |       |   Optional multiline detail text      |
 //  |  +-------+                                       |
 //  +--------------------------------------------------+
-@interface SettingsImageDetailTextCell : UITableViewCell
+@interface SettingsImageDetailTextCell : TableViewCell
 
 // Cell image.
 @property(nonatomic, strong) UIImage* image;
diff --git a/ios/chrome/browser/ui/settings/cells/settings_multiline_detail_item.h b/ios/chrome/browser/ui/settings/cells/settings_multiline_detail_item.h
index 5f07090..c6529b6 100644
--- a/ios/chrome/browser/ui/settings/cells/settings_multiline_detail_item.h
+++ b/ios/chrome/browser/ui/settings/cells/settings_multiline_detail_item.h
@@ -21,11 +21,11 @@
 
 @end
 
-// SettingsMultilineDetailCell implements an UITableViewCell
+// SettingsMultilineDetailCell implements an TableViewCell
 // subclass containing two text labels: a "main" label and a "detail" label.
 // The two labels are laid out on top of each other and can span on multiple
 // lines. This is to be used with a SettingsMultilineDetailItem.
-@interface SettingsMultilineDetailCell : UITableViewCell
+@interface SettingsMultilineDetailCell : TableViewCell
 
 // UILabels corresponding to |text| and |detailText| from the item.
 @property(nonatomic, readonly, strong) UILabel* textLabel;
diff --git a/ios/chrome/browser/ui/settings/cells/settings_switch_cell.h b/ios/chrome/browser/ui/settings/cells/settings_switch_cell.h
index 7df334a..3f3efe3c 100644
--- a/ios/chrome/browser/ui/settings/cells/settings_switch_cell.h
+++ b/ios/chrome/browser/ui/settings/cells/settings_switch_cell.h
@@ -7,11 +7,13 @@
 
 #import <UIKit/UIKit.h>
 
-// SettingsSwitchCell implements a UITableViewCell subclass containing an icon,
+#import "ios/chrome/browser/ui/table_view/cells/table_view_cell.h"
+
+// SettingsSwitchCell implements a TableViewCell subclass containing an icon,
 // a text label, a detail text and a switch.
 // If the preferred content size category is an accessibility category, the
 // switch is displayed below the label. Otherwise, it is on the trailing side.
-@interface SettingsSwitchCell : UITableViewCell
+@interface SettingsSwitchCell : TableViewCell
 
 // UILabel corresponding to |text| from the item.
 @property(nonatomic, readonly, strong) UILabel* textLabel;
diff --git a/ios/chrome/browser/ui/settings/cells/table_view_clear_browsing_data_item.mm b/ios/chrome/browser/ui/settings/cells/table_view_clear_browsing_data_item.mm
index 1490fcb..40ffff90 100644
--- a/ios/chrome/browser/ui/settings/cells/table_view_clear_browsing_data_item.mm
+++ b/ios/chrome/browser/ui/settings/cells/table_view_clear_browsing_data_item.mm
@@ -25,7 +25,7 @@
   return self;
 }
 
-- (void)configureCell:(UITableViewCell*)tableCell
+- (void)configureCell:(TableViewCell*)tableCell
            withStyler:(ChromeTableViewStyler*)styler {
   [super configureCell:tableCell withStyler:styler];
   TableViewTextCell* cell =
diff --git a/ios/chrome/browser/ui/settings/settings_root_table_view_controller.mm b/ios/chrome/browser/ui/settings/settings_root_table_view_controller.mm
index 5c49556..40e5bb0 100644
--- a/ios/chrome/browser/ui/settings/settings_root_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/settings_root_table_view_controller.mm
@@ -28,9 +28,6 @@
 // Estimated height of the header/footer, used to speed the constraints.
 const CGFloat kEstimatedHeaderFooterHeight = 35;
 
-// Color for the separator.
-const int kSeparatorColor = 0xE8EAED;
-
 enum SavedBarButtomItemPositionEnum {
   kUndefinedBarButtonItemPosition,
   kLeftBarButtonItemPosition,
@@ -123,7 +120,7 @@
   self.styler.tableViewSectionHeaderBlurEffect = nil;
   [super viewDidLoad];
   if (base::FeatureList::IsEnabled(kSettingsRefresh)) {
-    self.tableView.separatorColor = UIColorFromRGB(kSeparatorColor);
+    self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
   }
   self.styler.cellBackgroundColor = [UIColor whiteColor];
   self.styler.cellTitleColor = [UIColor blackColor];
diff --git a/ios/chrome/browser/ui/table_view/cells/BUILD.gn b/ios/chrome/browser/ui/table_view/cells/BUILD.gn
index 2ec7c81..30d47cb 100644
--- a/ios/chrome/browser/ui/table_view/cells/BUILD.gn
+++ b/ios/chrome/browser/ui/table_view/cells/BUILD.gn
@@ -6,6 +6,8 @@
   sources = [
     "table_view_activity_indicator_header_footer_item.h",
     "table_view_activity_indicator_header_footer_item.mm",
+    "table_view_cell.h",
+    "table_view_cell.mm",
     "table_view_cells_constants.h",
     "table_view_cells_constants.mm",
     "table_view_detail_text_item.h",
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_cell.h b/ios/chrome/browser/ui/table_view/cells/table_view_cell.h
new file mode 100644
index 0000000..ee7d077
--- /dev/null
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_cell.h
@@ -0,0 +1,23 @@
+// Copyright 2019 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 IOS_CHROME_BROWSER_UI_TABLE_VIEW_CELLS_TABLE_VIEW_CELL_H_
+#define IOS_CHROME_BROWSER_UI_TABLE_VIEW_CELLS_TABLE_VIEW_CELL_H_
+
+#import <UIKit/UIKit.h>
+
+// Base class for the TableViewCell used by the TableViewItems.
+@interface TableViewCell : UITableViewCell
+
+// Whether custom separator should be used. The separator can replace the
+// separator provided by UITableViewCell. It is a 0.5pt high line.
+@property(nonatomic, assign) BOOL useCustomSeparator;
+
+// View displayed as custom separator. Use this property to set the leading
+// anchor of the custom separator. Default is 16 points (priority high + 1).
+@property(nonatomic, strong, readonly) UIView* customSeparator;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_TABLE_VIEW_CELLS_TABLE_VIEW_CELL_H_
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_cell.mm b/ios/chrome/browser/ui/table_view/cells/table_view_cell.mm
new file mode 100644
index 0000000..bdba2de
--- /dev/null
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_cell.mm
@@ -0,0 +1,67 @@
+// Copyright 2019 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.
+
+#import "ios/chrome/browser/ui/table_view/cells/table_view_cell.h"
+
+#import "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h"
+#import "ios/chrome/browser/ui/util/ui_util.h"
+#import "ios/chrome/browser/ui/util/uikit_ui_util.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+const int kTableViewCustomSeparatorColor = 0xE8EAED;
+const CGFloat kTableViewCustomSeparatorHeight = 0.5;
+}  // namespace
+
+@interface TableViewCell ()
+@end
+
+@implementation TableViewCell
+
+- (instancetype)initWithStyle:(UITableViewCellStyle)style
+              reuseIdentifier:(NSString*)reuseIdentifier {
+  self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
+  if (self) {
+    _customSeparator = [[UIView alloc] init];
+    _customSeparator.translatesAutoresizingMaskIntoConstraints = NO;
+    _customSeparator.backgroundColor =
+        UIColorFromRGB(kTableViewCustomSeparatorColor);
+    [self addSubview:_customSeparator];
+
+    NSArray* constraints = @[
+      [_customSeparator.trailingAnchor
+          constraintEqualToAnchor:self.trailingAnchor],
+      [_customSeparator.bottomAnchor constraintEqualToAnchor:self.bottomAnchor],
+      [_customSeparator.heightAnchor
+          constraintEqualToConstant:AlignValueToPixel(
+                                        kTableViewCustomSeparatorHeight)],
+      [_customSeparator.leadingAnchor
+          constraintEqualToAnchor:self.leadingAnchor
+                         constant:kTableViewHorizontalSpacing],
+    ];
+    for (NSLayoutConstraint* constraint in constraints) {
+      // Have a priority higher than the default high but don't make it required
+      // to allow subclass to override it.
+      constraint.priority = UILayoutPriorityDefaultHigh + 1;
+    }
+    [NSLayoutConstraint activateConstraints:constraints];
+  }
+  return self;
+}
+
+- (void)setUseCustomSeparator:(BOOL)useCustomSeparator {
+  _useCustomSeparator = useCustomSeparator;
+  self.customSeparator.hidden = !useCustomSeparator;
+}
+
+#pragma mark - UITableViewCell
+
+- (void)prepareForReuse {
+  [super prepareForReuse];
+  self.useCustomSeparator = NO;
+}
+@end
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_detail_text_item.h b/ios/chrome/browser/ui/table_view/cells/table_view_detail_text_item.h
index 9d58a82..170e6e7e 100644
--- a/ios/chrome/browser/ui/table_view/cells/table_view_detail_text_item.h
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_detail_text_item.h
@@ -7,6 +7,7 @@
 
 #import <UIKit/UIKit.h>
 
+#import "ios/chrome/browser/ui/table_view/cells/table_view_cell.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_item.h"
 
 // TableViewDetailTextItem contains the model data for a
@@ -31,11 +32,11 @@
 
 @end
 
-// UITableViewCell that displays two text labels on top of each other. The text
+// TableViewCell that displays two text labels on top of each other. The text
 // labels are displaying on one line if the preferred content size isn't an
 // Accessibility category. Otherwise they are displayed on an unlimited number
 // of lines.
-@interface TableViewDetailTextCell : UITableViewCell
+@interface TableViewDetailTextCell : TableViewCell
 
 // The text to display.
 @property(nonatomic, readonly, strong) UILabel* textLabel;
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_image_item.h b/ios/chrome/browser/ui/table_view/cells/table_view_image_item.h
index cb16c58..dead71ea5 100644
--- a/ios/chrome/browser/ui/table_view/cells/table_view_image_item.h
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_image_item.h
@@ -7,6 +7,7 @@
 
 #import <UIKit/UIKit.h>
 
+#import "ios/chrome/browser/ui/table_view/cells/table_view_cell.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_item.h"
 
 // TableViewImageItem contains the model data for a TableViewImageCell.
@@ -26,7 +27,7 @@
 @end
 
 // TableViewImageCell contains a favicon, a title, and an optional chevron.
-@interface TableViewImageCell : UITableViewCell
+@interface TableViewImageCell : TableViewCell
 
 // The cell favicon imageView.
 @property(nonatomic, readonly, strong) UIImageView* imageView;
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_image_item.mm b/ios/chrome/browser/ui/table_view/cells/table_view_image_item.mm
index 02b226b..fcfa04b 100644
--- a/ios/chrome/browser/ui/table_view/cells/table_view_image_item.mm
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_image_item.mm
@@ -32,7 +32,7 @@
   return self;
 }
 
-- (void)configureCell:(UITableViewCell*)tableCell
+- (void)configureCell:(TableViewCell*)tableCell
            withStyler:(ChromeTableViewStyler*)styler {
   [super configureCell:tableCell withStyler:styler];
 
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_item.h b/ios/chrome/browser/ui/table_view/cells/table_view_item.h
index f20bfc3..5397e55b 100644
--- a/ios/chrome/browser/ui/table_view/cells/table_view_item.h
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_item.h
@@ -8,6 +8,7 @@
 #import <UIKit/UIKit.h>
 
 #import "ios/chrome/browser/ui/list_model/list_item.h"
+#import "ios/chrome/browser/ui/table_view/cells/table_view_cell.h"
 
 @class ChromeTableViewStyler;
 
@@ -17,13 +18,18 @@
 // The accessory type to display on the trailing edge of the cell.
 @property(nonatomic, assign) UITableViewCellAccessoryType accessoryType;
 
+// Whether custom separator should be used. The separator can replace the
+// separator provided by UITableViewCell. It is a 0.5pt high line. Default is
+// NO.
+@property(nonatomic, assign) BOOL useCustomSeparator;
+
 - (instancetype)initWithType:(NSInteger)type NS_DESIGNATED_INITIALIZER;
 
 // Configures the given cell with the item's information. Override this method
 // to specialize. At this level, only accessibility properties are ported from
 // the item to the cell.
 // The cell's class must match cellClass for the given instance.
-- (void)configureCell:(UITableViewCell*)cell
+- (void)configureCell:(TableViewCell*)cell
            withStyler:(ChromeTableViewStyler*)styler NS_REQUIRES_SUPER;
 
 @end
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_item.mm b/ios/chrome/browser/ui/table_view/cells/table_view_item.mm
index 9ac5a8e0..73f74fe 100644
--- a/ios/chrome/browser/ui/table_view/cells/table_view_item.mm
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_item.mm
@@ -15,16 +15,20 @@
 
 - (instancetype)initWithType:(NSInteger)type {
   if ((self = [super initWithType:type])) {
-    self.cellClass = [UITableViewCell class];
+    _useCustomSeparator = NO;
+
+    self.cellClass = [TableViewCell class];
   }
   return self;
 }
 
-- (void)configureCell:(UITableViewCell*)cell
+- (void)configureCell:(TableViewCell*)cell
            withStyler:(ChromeTableViewStyler*)styler {
   DCHECK(styler);
   DCHECK([cell class] == self.cellClass);
+  DCHECK([cell isKindOfClass:[TableViewCell class]]);
   cell.accessoryType = self.accessoryType;
+  cell.useCustomSeparator = self.useCustomSeparator;
   cell.accessibilityTraits = self.accessibilityTraits;
   cell.accessibilityIdentifier = self.accessibilityIdentifier;
   if (!cell.backgroundView) {
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_item_unittest.mm b/ios/chrome/browser/ui/table_view/cells/table_view_item_unittest.mm
index 2dbc3e06..59d081c 100644
--- a/ios/chrome/browser/ui/table_view/cells/table_view_item_unittest.mm
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_item_unittest.mm
@@ -21,8 +21,8 @@
   TableViewItem* item = [[TableViewItem alloc] initWithType:0];
   item.accessibilityIdentifier = @"test_identifier";
   item.accessibilityTraits = UIAccessibilityTraitButton;
-  UITableViewCell* cell = [[[item cellClass] alloc] init];
-  EXPECT_TRUE([cell isMemberOfClass:[UITableViewCell class]]);
+  TableViewCell* cell = [[[item cellClass] alloc] init];
+  EXPECT_TRUE([cell isMemberOfClass:[TableViewCell class]]);
   EXPECT_EQ(UIAccessibilityTraitNone, [cell accessibilityTraits]);
   EXPECT_FALSE([cell accessibilityIdentifier]);
 
@@ -34,8 +34,8 @@
 
 TEST_F(TableViewItemTest, ConfigureCellWithStyler) {
   TableViewItem* item = [[TableViewItem alloc] initWithType:0];
-  UITableViewCell* cell = [[[item cellClass] alloc] init];
-  ASSERT_TRUE([cell isMemberOfClass:[UITableViewCell class]]);
+  TableViewCell* cell = [[[item cellClass] alloc] init];
+  ASSERT_TRUE([cell isMemberOfClass:[TableViewCell class]]);
 
   ChromeTableViewStyler* styler = [[ChromeTableViewStyler alloc] init];
   UIColor* testColor = [UIColor redColor];
@@ -46,8 +46,8 @@
 
 TEST_F(TableViewItemTest, NoBackgroundColorIfBackgroundViewIsPresent) {
   TableViewItem* item = [[TableViewItem alloc] initWithType:0];
-  UITableViewCell* cell = [[[item cellClass] alloc] init];
-  ASSERT_TRUE([cell isMemberOfClass:[UITableViewCell class]]);
+  TableViewCell* cell = [[[item cellClass] alloc] init];
+  ASSERT_TRUE([cell isMemberOfClass:[TableViewCell class]]);
 
   // If a background view is present on the cell, the styler's background color
   // should be ignored.
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_text_button_item.h b/ios/chrome/browser/ui/table_view/cells/table_view_text_button_item.h
index 1c57af3..78d81bd52 100644
--- a/ios/chrome/browser/ui/table_view/cells/table_view_text_button_item.h
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_text_button_item.h
@@ -5,6 +5,7 @@
 #ifndef IOS_CHROME_BROWSER_UI_TABLE_VIEW_CELLS_TABLE_VIEW_TEXT_BUTTON_ITEM_H_
 #define IOS_CHROME_BROWSER_UI_TABLE_VIEW_CELLS_TABLE_VIEW_TEXT_BUTTON_ITEM_H_
 
+#import "ios/chrome/browser/ui/table_view/cells/table_view_cell.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_item.h"
 
 // TableViewTextButtonItem contains the model for
@@ -26,7 +27,7 @@
 
 // TableViewTextButtonCell contains a textLabel and a UIbutton
 // laid out vertically and centered.
-@interface TableViewTextButtonCell : UITableViewCell
+@interface TableViewTextButtonCell : TableViewCell
 
 // Cell text information.
 @property(nonatomic, strong) UILabel* textLabel;
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_text_button_item.mm b/ios/chrome/browser/ui/table_view/cells/table_view_text_button_item.mm
index 702a95b..8e40c89d 100644
--- a/ios/chrome/browser/ui/table_view/cells/table_view_text_button_item.mm
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_text_button_item.mm
@@ -46,7 +46,7 @@
   return self;
 }
 
-- (void)configureCell:(UITableViewCell*)tableCell
+- (void)configureCell:(TableViewCell*)tableCell
            withStyler:(ChromeTableViewStyler*)styler {
   [super configureCell:tableCell withStyler:styler];
   TableViewTextButtonCell* cell =
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_text_item.h b/ios/chrome/browser/ui/table_view/cells/table_view_text_item.h
index a26c511..140deb9 100644
--- a/ios/chrome/browser/ui/table_view/cells/table_view_text_item.h
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_text_item.h
@@ -7,6 +7,7 @@
 
 #import <UIKit/UIKit.h>
 
+#import "ios/chrome/browser/ui/table_view/cells/table_view_cell.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_item.h"
 
 // TableViewTextItem contains the model data for a TableViewTextCell.
@@ -31,8 +32,8 @@
 
 @end
 
-// UITableViewCell that displays a text label.
-@interface TableViewTextCell : UITableViewCell
+// TableViewCell that displays a text label.
+@interface TableViewTextCell : TableViewCell
 
 // The text to display.
 @property(nonatomic, readonly, strong) UILabel* textLabel;
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_text_item.mm b/ios/chrome/browser/ui/table_view/cells/table_view_text_item.mm
index caec2cb..59b74fa 100644
--- a/ios/chrome/browser/ui/table_view/cells/table_view_text_item.mm
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_text_item.mm
@@ -33,7 +33,7 @@
   return self;
 }
 
-- (void)configureCell:(UITableViewCell*)tableCell
+- (void)configureCell:(TableViewCell*)tableCell
            withStyler:(ChromeTableViewStyler*)styler {
   [super configureCell:tableCell withStyler:styler];
   TableViewTextCell* cell =
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_text_link_item.h b/ios/chrome/browser/ui/table_view/cells/table_view_text_link_item.h
index d3993c83..ff48e03 100644
--- a/ios/chrome/browser/ui/table_view/cells/table_view_text_link_item.h
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_text_link_item.h
@@ -7,6 +7,7 @@
 
 #import <UIKit/UIKit.h>
 
+#import "ios/chrome/browser/ui/table_view/cells/table_view_cell.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_item.h"
 
 class GURL;
@@ -27,8 +28,8 @@
 @property(nonatomic, assign) GURL linkURL;
 @end
 
-// UITableViewCell that displays a text label that might contain a link.
-@interface TableViewTextLinkCell : UITableViewCell
+// TableViewCell that displays a text label that might contain a link.
+@interface TableViewTextLinkCell : TableViewCell
 // The text to display.
 @property(nonatomic, readonly, strong) UILabel* textLabel;
 // Delegate for the TableViewTextLinkCell. Is notified when a link is
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_text_link_item.mm b/ios/chrome/browser/ui/table_view/cells/table_view_text_link_item.mm
index 6bf4c9b1..2b10243 100644
--- a/ios/chrome/browser/ui/table_view/cells/table_view_text_link_item.mm
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_text_link_item.mm
@@ -35,7 +35,7 @@
   return self;
 }
 
-- (void)configureCell:(UITableViewCell*)tableCell
+- (void)configureCell:(TableViewCell*)tableCell
            withStyler:(ChromeTableViewStyler*)styler {
   [super configureCell:tableCell withStyler:styler];
   TableViewTextLinkCell* cell =
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_url_item.h b/ios/chrome/browser/ui/table_view/cells/table_view_url_item.h
index d44e41a53..4cada52 100644
--- a/ios/chrome/browser/ui/table_view/cells/table_view_url_item.h
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_url_item.h
@@ -7,6 +7,7 @@
 
 #import <UIKit/UIKit.h>
 
+#import "ios/chrome/browser/ui/table_view/cells/table_view_cell.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_item.h"
 
 class GURL;
@@ -37,7 +38,7 @@
 // contains a favicon, a title, a URL, and optionally some metadata such as a
 // timestamp or a file size. After configuring the cell, make sure to call
 // configureUILayout:.
-@interface TableViewURLCell : UITableViewCell
+@interface TableViewURLCell : TableViewCell
 
 // The imageview that is displayed on the leading edge of the cell.  This
 // contains a favicon composited on top of an off-white background.
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_url_item.mm b/ios/chrome/browser/ui/table_view/cells/table_view_url_item.mm
index 00d82a514..93f7c44 100644
--- a/ios/chrome/browser/ui/table_view/cells/table_view_url_item.mm
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_url_item.mm
@@ -51,7 +51,7 @@
   return self;
 }
 
-- (void)configureCell:(UITableViewCell*)tableCell
+- (void)configureCell:(TableViewCell*)tableCell
            withStyler:(ChromeTableViewStyler*)styler {
   [super configureCell:tableCell withStyler:styler];
 
diff --git a/ios/chrome/browser/ui/table_view/chrome_table_view_controller.mm b/ios/chrome/browser/ui/table_view/chrome_table_view_controller.mm
index 6401926..00dfc5a44 100644
--- a/ios/chrome/browser/ui/table_view/chrome_table_view_controller.mm
+++ b/ios/chrome/browser/ui/table_view/chrome_table_view_controller.mm
@@ -5,8 +5,10 @@
 #import "ios/chrome/browser/ui/table_view/chrome_table_view_controller.h"
 
 #include "base/logging.h"
+#include "base/mac/foundation_util.h"
 #import "ios/chrome/browser/ui/material_components/chrome_app_bar_view_controller.h"
 #import "ios/chrome/browser/ui/material_components/utils.h"
+#import "ios/chrome/browser/ui/table_view/cells/table_view_cell.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_header_footer_item.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_item.h"
 #import "ios/chrome/browser/ui/table_view/chrome_table_view_styler.h"
@@ -195,7 +197,9 @@
 
     // |cell| may be nil if the row is not currently on screen.
     if (cell) {
-      [item configureCell:cell withStyler:self.styler];
+      TableViewCell* tableViewCell =
+          base::mac::ObjCCastStrict<TableViewCell>(cell);
+      [item configureCell:tableViewCell withStyler:self.styler];
     }
   }
 }
@@ -226,7 +230,8 @@
   UITableViewCell* cell =
       [self.tableView dequeueReusableCellWithIdentifier:reuseIdentifier
                                            forIndexPath:indexPath];
-  [item configureCell:cell withStyler:self.styler];
+  TableViewCell* tableViewCell = base::mac::ObjCCastStrict<TableViewCell>(cell);
+  [item configureCell:tableViewCell withStyler:self.styler];
 
   return cell;
 }
diff --git a/ios/chrome/browser/ui/table_view/chrome_table_view_controller_unittest.mm b/ios/chrome/browser/ui/table_view/chrome_table_view_controller_unittest.mm
index 6a5c571..61879f3 100644
--- a/ios/chrome/browser/ui/table_view/chrome_table_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/table_view/chrome_table_view_controller_unittest.mm
@@ -24,7 +24,7 @@
 
 @synthesize configureCellCalled = _configureCellCalled;
 
-- (void)configureCell:(UITableViewCell*)cell
+- (void)configureCell:(TableViewCell*)cell
            withStyler:(ChromeTableViewStyler*)styler {
   self.configureCellCalled = YES;
   [super configureCell:cell withStyler:styler];
diff --git a/ios/chrome/browser/ui/translate/cells/translate_popup_menu_item.h b/ios/chrome/browser/ui/translate/cells/translate_popup_menu_item.h
index 0714d26d..a8e1825 100644
--- a/ios/chrome/browser/ui/translate/cells/translate_popup_menu_item.h
+++ b/ios/chrome/browser/ui/translate/cells/translate_popup_menu_item.h
@@ -22,7 +22,7 @@
 @end
 
 // Associated cell for a TranslatePopupMenuItem.
-@interface TranslatePopupMenuCell : UITableViewCell
+@interface TranslatePopupMenuCell : TableViewCell
 
 - (void)setTitle:(NSString*)title;
 
diff --git a/ios/chrome/browser/ui/util/ui_util.mm b/ios/chrome/browser/ui/util/ui_util.mm
index 202ffe1..9444b19 100644
--- a/ios/chrome/browser/ui/util/ui_util.mm
+++ b/ios/chrome/browser/ui/util/ui_util.mm
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ios/chrome/browser/ui/util/ui_util.h"
+#import "ios/chrome/browser/ui/util/ui_util.h"
 
 #import <UIKit/UIKit.h>
 #include <limits>
diff --git a/ios/chrome/browser/web/error_page_egtest.mm b/ios/chrome/browser/web/error_page_egtest.mm
index 3af4c02..6fad78c 100644
--- a/ios/chrome/browser/web/error_page_egtest.mm
+++ b/ios/chrome/browser/web/error_page_egtest.mm
@@ -44,9 +44,6 @@
       &net::test_server::HandlePrefixedRequest, "/echo-query",
       base::BindRepeating(&testing::HandleEchoQueryOrCloseSocket,
                           base::ConstRef(_serverRespondsWithContent))));
-  self.testServer->RegisterRequestHandler(
-      base::BindRepeating(&net::test_server::HandlePrefixedRequest, "/iframe",
-                          base::BindRepeating(&testing::HandleIFrame)));
 
   GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");
 }
diff --git a/ios/chrome/browser/web/visible_url_egtest.mm b/ios/chrome/browser/web/visible_url_egtest.mm
index c3103456..991bdba 100644
--- a/ios/chrome/browser/web/visible_url_egtest.mm
+++ b/ios/chrome/browser/web/visible_url_egtest.mm
@@ -605,18 +605,10 @@
 
   // Make server respond so URL1 becomes committed.
   [self setServerPaused:NO];
-  if (base::FeatureList::IsEnabled(web::features::kWebFrameMessaging)) {
-    // With frame messaging, only one back is executed. This is the expected
-    // behavior.
-    [ChromeEarlGrey waitForWebViewContainingText:kTestPage2];
-    [[EarlGrey selectElementWithMatcher:OmniboxText(_testURL2.GetContent())]
-        assertWithMatcher:grey_notNil()];
-  } else {
-    // TODO(crbug.com/866406): fix the test to have documented behavior.
-    [ChromeEarlGrey waitForWebViewContainingText:kTestPage1];
-    [[EarlGrey selectElementWithMatcher:OmniboxText(_testURL1.GetContent())]
-        assertWithMatcher:grey_notNil()];
-  }
+  // TODO(crbug.com/866406): fix the test to have documented behavior.
+  [ChromeEarlGrey waitForWebViewContainingText:kTestPage1];
+  [[EarlGrey selectElementWithMatcher:OmniboxText(_testURL1.GetContent())]
+      assertWithMatcher:grey_notNil()];
 }
 
 #pragma mark -
diff --git a/ios/web/BUILD.gn b/ios/web/BUILD.gn
index 4b8d812f..4f4bf51 100644
--- a/ios/web/BUILD.gn
+++ b/ios/web/BUILD.gn
@@ -174,6 +174,7 @@
     ":ios_web_web_state_unittests",
     ":ios_web_webui_unittests",
     "//ios/testing:http_server_bundle_data",
+    "//ios/web/browsing_data:browsing_data_unittests",
     "//ios/web/download:download_unittests",
     "//ios/web/interstitials:interstitials_unittests",
   ]
diff --git a/ios/web/browsing_data/BUILD.gn b/ios/web/browsing_data/BUILD.gn
new file mode 100644
index 0000000..b187e9e
--- /dev/null
+++ b/ios/web/browsing_data/BUILD.gn
@@ -0,0 +1,32 @@
+# Copyright 2019 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.
+
+source_set("browsing_data") {
+  sources = [
+    "browsing_data_remover.h",
+    "browsing_data_remover.mm",
+    "browsing_data_remover_observer.h",
+    "browsing_data_removing_util.mm",
+  ]
+
+  deps = [
+    "//ios/web/public",
+  ]
+
+  configs += [ "//build/config/compiler:enable_arc" ]
+}
+
+source_set("browsing_data_unittests") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  testonly = true
+  deps = [
+    ":browsing_data",
+    "//ios/web/public/test/fakes",
+    "//testing/gtest",
+  ]
+
+  sources = [
+    "browsing_data_remover_unittest.mm",
+  ]
+}
diff --git a/ios/web/browsing_data/browsing_data_remover.h b/ios/web/browsing_data/browsing_data_remover.h
new file mode 100644
index 0000000..1f51abf
--- /dev/null
+++ b/ios/web/browsing_data/browsing_data_remover.h
@@ -0,0 +1,44 @@
+// Copyright 2019 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 IOS_WEB_BROWSING_DATA_BROWSING_DATA_REMOVER_H_
+#define IOS_WEB_BROWSING_DATA_BROWSING_DATA_REMOVER_H_
+
+#import <Foundation/Foundation.h>
+
+#include "base/supports_user_data.h"
+#import "ios/web/public/browsing_data_removing_util.h"
+
+@protocol BrowsingDataRemoverObserver;
+
+namespace web {
+
+class BrowserState;
+
+// Class used to removes the browsing data stored by the WebKit data store.
+class BrowsingDataRemover : public base::SupportsUserData::Data {
+ public:
+  explicit BrowsingDataRemover(web::BrowserState* browser_state);
+  ~BrowsingDataRemover() override;
+
+  // Returns the BrowsingDataRemover associated with |browser_state.|
+  // Lazily creates one if an BrowsingDataRemover is not already associated with
+  // the |browser_state|. |browser_state| cannot be a nullptr.
+  static BrowsingDataRemover* FromBrowserState(BrowserState* browser_state);
+
+  // Clears the browsing data.
+  void ClearBrowsingData(ClearBrowsingDataMask types);
+
+  void AddObserver(id<BrowsingDataRemoverObserver> observer);
+  void RemoveObserver(id<BrowsingDataRemoverObserver> observer);
+
+ private:
+  web::BrowserState* browser_state_;  // weak, owns this object.
+  // The list of observers. Holds weak references.
+  NSHashTable<id<BrowsingDataRemoverObserver>>* observers_list_;
+};
+
+}  // namespace web
+
+#endif  // IOS_WEB_BROWSING_DATA_BROWSING_DATA_REMOVER_H_
diff --git a/ios/web/browsing_data/browsing_data_remover.mm b/ios/web/browsing_data/browsing_data_remover.mm
new file mode 100644
index 0000000..4d73ebd
--- /dev/null
+++ b/ios/web/browsing_data/browsing_data_remover.mm
@@ -0,0 +1,59 @@
+// Copyright 2019 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.
+
+#import "ios/web/browsing_data/browsing_data_remover.h"
+
+#import "ios/web/browsing_data/browsing_data_remover_observer.h"
+#import "ios/web/public/browser_state.h"
+#import "ios/web/public/web_thread.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+const char kWebBrowsingDataRemoverKeyName[] = "web_browsing_data_remover";
+}  // namespace
+
+namespace web {
+
+BrowsingDataRemover::BrowsingDataRemover(web::BrowserState* browser_state)
+    : browser_state_(browser_state) {
+  DCHECK(browser_state_);
+  observers_list_ = [NSHashTable weakObjectsHashTable];
+}
+
+BrowsingDataRemover::~BrowsingDataRemover() {}
+
+// static
+BrowsingDataRemover* BrowsingDataRemover::FromBrowserState(
+    BrowserState* browser_state) {
+  DCHECK(browser_state);
+
+  BrowsingDataRemover* browsing_data_remover =
+      static_cast<BrowsingDataRemover*>(
+          browser_state->GetUserData(kWebBrowsingDataRemoverKeyName));
+  if (!browsing_data_remover) {
+    browsing_data_remover = new BrowsingDataRemover(browser_state);
+    browser_state->SetUserData(kWebBrowsingDataRemoverKeyName,
+                               base::WrapUnique(browsing_data_remover));
+  }
+  return browsing_data_remover;
+}
+
+void BrowsingDataRemover::ClearBrowsingData(ClearBrowsingDataMask types) {
+  // TODO(crbug.com/619783):implement this.
+}
+
+void BrowsingDataRemover::AddObserver(
+    id<BrowsingDataRemoverObserver> observer) {
+  [observers_list_ addObject:observer];
+}
+
+void BrowsingDataRemover::RemoveObserver(
+    id<BrowsingDataRemoverObserver> observer) {
+  [observers_list_ removeObject:observer];
+}
+
+}  // namespace web
diff --git a/ios/web/browsing_data/browsing_data_remover_observer.h b/ios/web/browsing_data/browsing_data_remover_observer.h
new file mode 100644
index 0000000..60fef80e
--- /dev/null
+++ b/ios/web/browsing_data/browsing_data_remover_observer.h
@@ -0,0 +1,15 @@
+// Copyright 2019 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 IOS_WEB_BROWSING_DATA_BROWSING_DATA_REMOVER_OBSERVER_H_
+#define IOS_WEB_BROWSING_DATA_BROWSING_DATA_REMOVER_OBSERVER_H_
+
+#import <Foundation/Foundation.h>
+
+// Protocol used to observe the BrowsingDataRemover.
+@protocol BrowsingDataRemoverObserver
+
+@end
+
+#endif  // IOS_WEB_BROWSING_DATA_BROWSING_DATA_REMOVER_OBSERVER_H_
diff --git a/ios/web/browsing_data/browsing_data_remover_unittest.mm b/ios/web/browsing_data/browsing_data_remover_unittest.mm
new file mode 100644
index 0000000..031e5d0
--- /dev/null
+++ b/ios/web/browsing_data/browsing_data_remover_unittest.mm
@@ -0,0 +1,40 @@
+// Copyright 2019 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.
+
+#import "ios/web/browsing_data/browsing_data_remover.h"
+
+#include "ios/web/public/test/fakes/test_browser_state.h"
+#include "testing/platform_test.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace web {
+
+class BrowsingDataRemoverTest : public PlatformTest {
+ protected:
+  BrowsingDataRemover* GetRemover() {
+    return BrowsingDataRemover::FromBrowserState(&browser_state_);
+  }
+  TestBrowserState browser_state_;
+};
+
+TEST_F(BrowsingDataRemoverTest, DifferentRemoverForDifferentBrowserState) {
+  TestBrowserState browser_state_1;
+  TestBrowserState browser_state_2;
+
+  BrowsingDataRemover* remover_1 =
+      BrowsingDataRemover::FromBrowserState(&browser_state_1);
+  BrowsingDataRemover* remover_2 =
+      BrowsingDataRemover::FromBrowserState(&browser_state_2);
+
+  EXPECT_NE(remover_1, remover_2);
+
+  BrowsingDataRemover* remover_1_again =
+      BrowsingDataRemover::FromBrowserState(&browser_state_1);
+  EXPECT_EQ(remover_1_again, remover_1);
+}
+
+}  // namespace web
diff --git a/ios/web/browsing_data/browsing_data_removing_util.mm b/ios/web/browsing_data/browsing_data_removing_util.mm
new file mode 100644
index 0000000..912e0d6
--- /dev/null
+++ b/ios/web/browsing_data/browsing_data_removing_util.mm
@@ -0,0 +1,22 @@
+// Copyright 2019 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.
+
+#import "ios/web/public/browsing_data_removing_util.h"
+
+#import "ios/web/browsing_data/browsing_data_remover.h"
+#import "ios/web/public/browser_state.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace web {
+
+void ClearBrowsingData(BrowserState* browser_state,
+                       ClearBrowsingDataMask types) {
+  BrowsingDataRemover::FromBrowserState(browser_state)
+      ->ClearBrowsingData(types);
+}
+
+}  // namespace web
diff --git a/ios/web/features.mm b/ios/web/features.mm
index 8c9e0223..6f55e193 100644
--- a/ios/web/features.mm
+++ b/ios/web/features.mm
@@ -11,7 +11,7 @@
     "IgnoresViewportScaleLimits", base::FEATURE_DISABLED_BY_DEFAULT};
 
 const base::Feature kWebFrameMessaging{"WebFrameMessaging",
-                                       base::FEATURE_DISABLED_BY_DEFAULT};
+                                       base::FEATURE_ENABLED_BY_DEFAULT};
 
 const base::Feature kSlimNavigationManager{"SlimNavigationManager",
                                            base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/ios/web/public/BUILD.gn b/ios/web/public/BUILD.gn
index 9b64373..1c60fae 100644
--- a/ios/web/public/BUILD.gn
+++ b/ios/web/public/BUILD.gn
@@ -21,6 +21,7 @@
     "block_types.h",
     "browser_state.h",
     "browser_url_rewriter.h",
+    "browsing_data_removing_util.h",
     "cert_policy.h",
     "certificate_policy_cache.h",
     "crw_navigation_item_storage.h",
diff --git a/ios/web/public/browsing_data_removing_util.h b/ios/web/public/browsing_data_removing_util.h
new file mode 100644
index 0000000..a1c156e
--- /dev/null
+++ b/ios/web/public/browsing_data_removing_util.h
@@ -0,0 +1,70 @@
+// Copyright 2019 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 IOS_WEB_PUBLIC_BROWSING_DATA_REMOVING_UTIL_H_
+#define IOS_WEB_PUBLIC_BROWSING_DATA_REMOVING_UTIL_H_
+
+#include <type_traits>
+
+namespace web {
+
+class BrowserState;
+
+// Mask used to control which data to remove when clearing browsing
+// data.
+enum class ClearBrowsingDataMask {
+  kRemoveNothing = 0,
+
+  kRemoveAppCache = 1 << 0,
+  kRemoveCookies = 1 << 1,
+  kRemoveIndexedDB = 1 << 2,
+  kRemoveLocalStorage = 1 << 3,
+  kRemoveWebSQL = 1 << 4,
+  kRemoveCacheStorage = 1 << 5,
+  kRemoveVisitedLinks = 1 << 6,
+
+};
+
+// Clears the browsing data store in the Web layer.
+void ClearBrowsingData(BrowserState* browser_state,
+                       ClearBrowsingDataMask types);
+
+// Implementation of bitwise "or", "and" operators and the corresponding
+// assignment operators too (as those are not automatically defined for
+// "class enum").
+constexpr ClearBrowsingDataMask operator|(ClearBrowsingDataMask lhs,
+                                          ClearBrowsingDataMask rhs) {
+  return static_cast<ClearBrowsingDataMask>(
+      static_cast<std::underlying_type<ClearBrowsingDataMask>::type>(lhs) |
+      static_cast<std::underlying_type<ClearBrowsingDataMask>::type>(rhs));
+}
+
+constexpr ClearBrowsingDataMask operator&(ClearBrowsingDataMask lhs,
+                                          ClearBrowsingDataMask rhs) {
+  return static_cast<ClearBrowsingDataMask>(
+      static_cast<std::underlying_type<ClearBrowsingDataMask>::type>(lhs) &
+      static_cast<std::underlying_type<ClearBrowsingDataMask>::type>(rhs));
+}
+
+inline ClearBrowsingDataMask& operator|=(ClearBrowsingDataMask& lhs,
+                                         ClearBrowsingDataMask rhs) {
+  lhs = lhs | rhs;
+  return lhs;
+}
+
+inline ClearBrowsingDataMask& operator&=(ClearBrowsingDataMask& lhs,
+                                         ClearBrowsingDataMask rhs) {
+  lhs = lhs & rhs;
+  return lhs;
+}
+
+// Returns whether the |flag| is set in |mask|.
+constexpr bool IsRemoveDataMaskSet(ClearBrowsingDataMask mask,
+                                   ClearBrowsingDataMask flag) {
+  return (mask & flag) == flag;
+}
+
+}  // namespace web
+
+#endif  // IOS_WEB_PUBLIC_BROWSING_DATA_REMOVING_UTIL_H_
diff --git a/ios/web/web_state/ui/BUILD.gn b/ios/web/web_state/ui/BUILD.gn
index e204894..30341c0 100644
--- a/ios/web/web_state/ui/BUILD.gn
+++ b/ios/web/web_state/ui/BUILD.gn
@@ -14,6 +14,7 @@
     "//base",
     "//ios/net",
     "//ios/web:core",
+    "//ios/web/browsing_data",
     "//ios/web/find_in_page",
     "//ios/web/interstitials",
     "//ios/web/navigation",
diff --git a/ios/web_view/internal/sync/cwv_sync_controller_unittest.mm b/ios/web_view/internal/sync/cwv_sync_controller_unittest.mm
index 3dd274c5..3ff5a8f1 100644
--- a/ios/web_view/internal/sync/cwv_sync_controller_unittest.mm
+++ b/ios/web_view/internal/sync/cwv_sync_controller_unittest.mm
@@ -13,15 +13,7 @@
 #include "base/test/bind_test_util.h"
 #include "components/browser_sync/profile_sync_service_mock.h"
 #include "components/browser_sync/profile_sync_test_util.h"
-#include "components/signin/core/browser/account_tracker_service.h"
-#include "components/signin/core/browser/fake_account_fetcher_service.h"
-#include "components/signin/core/browser/fake_profile_oauth2_token_service.h"
-#include "components/signin/core/browser/fake_signin_manager.h"
 #include "components/signin/core/browser/signin_error_controller.h"
-#include "components/signin/core/browser/test_signin_client.h"
-#include "components/signin/ios/browser/fake_profile_oauth2_token_service_ios_provider.h"
-#include "components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.h"
-#include "components/signin/ios/browser/profile_oauth2_token_service_ios_provider.h"
 #include "components/sync/driver/sync_service_observer.h"
 #include "google_apis/gaia/google_service_auth_error.h"
 #import "ios/web/public/test/fakes/test_web_state.h"
@@ -32,6 +24,7 @@
 #import "ios/web_view/public/cwv_sync_controller_data_source.h"
 #import "ios/web_view/public/cwv_sync_controller_delegate.h"
 #include "services/identity/public/cpp/identity_test_environment.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #import "testing/gtest_mac.h"
@@ -55,24 +48,6 @@
  protected:
   CWVSyncControllerTest()
       : browser_state_(/*off_the_record=*/false),
-        signin_client_(browser_state_.GetPrefs()),
-        token_service_delegate_(new ProfileOAuth2TokenServiceIOSDelegate(
-            &signin_client_,
-            std::make_unique<FakeProfileOAuth2TokenServiceIOSProvider>(),
-            &account_tracker_service_)),
-        token_service_(browser_state_.GetPrefs(),
-                       std::unique_ptr<ProfileOAuth2TokenServiceIOSDelegate>(
-                           token_service_delegate_)),
-        gaia_cookie_manager_service_(&token_service_, &signin_client_),
-        signin_manager_(&signin_client_,
-                        &token_service_,
-                        &account_tracker_service_,
-                        &gaia_cookie_manager_service_),
-        identity_test_env_(&account_tracker_service_,
-                           &account_fetcher_service_,
-                           &token_service_,
-                           &signin_manager_,
-                           &gaia_cookie_manager_service_),
         signin_error_controller_(
             SigninErrorController::AccountMode::ANY_ACCOUNT,
             identity_test_env_.identity_manager()) {
@@ -89,14 +64,6 @@
         std::make_unique<browser_sync::ProfileSyncServiceMock>(
             std::move(init_params));
 
-    account_tracker_service_.Initialize(browser_state_.GetPrefs(),
-                                        base::FilePath());
-    account_fetcher_service_.Initialize(&signin_client_, &token_service_,
-                                        &account_tracker_service_,
-                                        std::make_unique<TestImageDecoder>());
-    signin_manager_.Initialize(
-        ApplicationContext::GetInstance()->GetLocalState());
-
     EXPECT_CALL(*profile_sync_service_, AddObserver(_))
         .WillOnce(Invoke(this, &CWVSyncControllerTest::AddObserver));
 
@@ -108,8 +75,6 @@
 
   ~CWVSyncControllerTest() override {
     EXPECT_CALL(*profile_sync_service_, RemoveObserver(_));
-    account_fetcher_service_.Shutdown();
-    account_tracker_service_.Shutdown();
   }
 
   void AddObserver(syncer::SyncServiceObserver* observer) {
@@ -125,17 +90,6 @@
   ios_web_view::WebViewBrowserState browser_state_;
   web::TestWebState web_state_;
   browser_sync::ProfileSyncServiceBundle profile_sync_service_bundle_;
-  AccountTrackerService account_tracker_service_;
-  FakeAccountFetcherService account_fetcher_service_;
-  TestSigninClient signin_client_;
-
-  // Weak, owned by the token service.
-  ProfileOAuth2TokenServiceIOSDelegate* token_service_delegate_;
-
-  FakeProfileOAuth2TokenService token_service_;
-
-  GaiaCookieManagerService gaia_cookie_manager_service_;
-  FakeSigninManager signin_manager_;
   identity::IdentityTestEnvironment identity_test_env_;
   SigninErrorController signin_error_controller_;
   std::unique_ptr<browser_sync::ProfileSyncServiceMock> profile_sync_service_;
@@ -199,10 +153,14 @@
     // Create authentication error.
     GoogleServiceAuthError auth_error(
         GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
-    std::string account_id = account_tracker_service_.SeedAccountInfo(
-        "gaia_id", "email@example.com");
-    token_service_delegate_->AddOrUpdateAccount(account_id);
-    token_service_delegate_->UpdateAuthError(account_id, auth_error);
+    std::string account_id =
+        identity_test_env_.MakePrimaryAccountAvailable("email@example.com")
+            .account_id;
+    // TODO(crbug.com/930094): Eliminate this.
+    identity_test_env_.identity_manager()->LegacyAddAccountFromSystem(
+        account_id);
+    identity_test_env_.UpdatePersistentErrorOfRefreshTokenForAccount(
+        account_id, auth_error);
 
     [[delegate expect] syncController:sync_controller_
                 didStopSyncWithReason:CWVStopSyncReasonServer];
diff --git a/media/blink/webmediaplayer_impl_unittest.cc b/media/blink/webmediaplayer_impl_unittest.cc
index 9b0b81f..e46686b 100644
--- a/media/blink/webmediaplayer_impl_unittest.cc
+++ b/media/blink/webmediaplayer_impl_unittest.cc
@@ -129,12 +129,6 @@
   MOCK_METHOD1(RemoveTextTrack, void(blink::WebInbandTextTrack*));
   MOCK_METHOD1(MediaSourceOpened, void(blink::WebMediaSource*));
   MOCK_METHOD1(RequestSeek, void(double));
-  MOCK_METHOD1(RemoteRouteAvailabilityChanged,
-               void(blink::WebRemotePlaybackAvailability));
-  MOCK_METHOD0(ConnectedToRemoteDevice, void());
-  MOCK_METHOD0(DisconnectedFromRemoteDevice, void());
-  MOCK_METHOD0(CancelledRemotePlaybackRequest, void());
-  MOCK_METHOD0(RemotePlaybackStarted, void());
   MOCK_METHOD2(RemotePlaybackCompatibilityChanged,
                void(const blink::WebURL&, bool));
   MOCK_METHOD1(OnBecamePersistentVideo, void(bool));
diff --git a/net/base/url_util.cc b/net/base/url_util.cc
index 57d180e..3fe45e9c 100644
--- a/net/base/url_util.cc
+++ b/net/base/url_util.cc
@@ -21,6 +21,7 @@
 #include "url/gurl.h"
 #include "url/url_canon.h"
 #include "url/url_canon_ip.h"
+#include "url/url_constants.h"
 
 namespace net {
 
@@ -372,6 +373,14 @@
   return url.ReplaceComponents(replacements);
 }
 
+GURL ChangeWebSocketSchemeToHttpScheme(const GURL& url) {
+  DCHECK(url.SchemeIsWSOrWSS());
+  GURL::Replacements replace_scheme;
+  replace_scheme.SetSchemeStr(url.SchemeIs(url::kWssScheme) ? url::kHttpsScheme
+                                                            : url::kHttpScheme);
+  return url.ReplaceComponents(replace_scheme);
+}
+
 void GetIdentityFromURL(const GURL& url,
                         base::string16* username,
                         base::string16* password) {
diff --git a/net/base/url_util.h b/net/base/url_util.h
index 0b820a70..c21c96e 100644
--- a/net/base/url_util.h
+++ b/net/base/url_util.h
@@ -156,6 +156,12 @@
 //   - reference section
 NET_EXPORT GURL SimplifyUrlForRequest(const GURL& url);
 
+// Changes scheme "ws" to "http" and "wss" to "https". This is useful for origin
+// checks and authentication, where WebSocket URLs are treated as if they were
+// HTTP. It is an error to call this function with a url with a scheme other
+// than "ws" or "wss".
+NET_EXPORT GURL ChangeWebSocketSchemeToHttpScheme(const GURL& url);
+
 // Extracts the unescaped username/password from |url|, saving the results
 // into |*username| and |*password|.
 NET_EXPORT_PRIVATE void GetIdentityFromURL(const GURL& url,
diff --git a/net/base/url_util_unittest.cc b/net/base/url_util_unittest.cc
index 2aa0f184..f57d89ef 100644
--- a/net/base/url_util_unittest.cc
+++ b/net/base/url_util_unittest.cc
@@ -479,6 +479,21 @@
   }
 }
 
+TEST(UrlUtilTest, ChangeWebSocketSchemeToHttpScheme) {
+  struct {
+    const char* const input_url;
+    const char* const expected_output_url;
+  } tests[] = {
+      {"ws://google.com:78/path?query=1", "http://google.com:78/path?query=1"},
+      {"wss://google.com:441/path?q=1", "https://google.com:441/path?q=1"}};
+  for (const auto& test : tests) {
+    GURL input_url(test.input_url);
+    GURL expected_output_url(test.expected_output_url);
+    EXPECT_EQ(expected_output_url,
+              ChangeWebSocketSchemeToHttpScheme(input_url));
+  }
+}
+
 TEST(UrlUtilTest, GetIdentityFromURL) {
   struct {
     const char* const input_url;
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc
index b87afc1..397f54b 100644
--- a/net/http/http_network_transaction.cc
+++ b/net/http/http_network_transaction.cc
@@ -1868,15 +1868,7 @@
     }
     case HttpAuth::AUTH_SERVER:
       if (ForWebSocketHandshake()) {
-        const GURL& url = request_->url;
-        url::Replacements<char> ws_to_http;
-        if (url.SchemeIs("ws")) {
-          ws_to_http.SetScheme("http", url::Component(0, 4));
-        } else {
-          DCHECK(url.SchemeIs("wss"));
-          ws_to_http.SetScheme("https", url::Component(0, 5));
-        }
-        return url.ReplaceComponents(ws_to_http);
+        return net::ChangeWebSocketSchemeToHttpScheme(request_->url);
       }
       return request_->url;
     default:
diff --git a/services/identity/DEPS b/services/identity/DEPS
index e875947..8a5277bb 100644
--- a/services/identity/DEPS
+++ b/services/identity/DEPS
@@ -1,6 +1,8 @@
 include_rules = [
+  "+components/prefs/pref_service.h",
   "+components/signin/core/browser/account_info.h",
   "+components/signin/core/browser/account_tracker_service.h",
+  "+components/signin/core/browser/device_id_helper.h",
   "+components/signin/core/browser/fake_profile_oauth2_token_service.h",
   "+components/signin/core/browser/fake_signin_manager.h",
   "+components/signin/core/browser/profile_oauth2_token_service.h",
diff --git a/services/identity/public/cpp/BUILD.gn b/services/identity/public/cpp/BUILD.gn
index 2d4266a..ceb24d15 100644
--- a/services/identity/public/cpp/BUILD.gn
+++ b/services/identity/public/cpp/BUILD.gn
@@ -44,6 +44,10 @@
     "//services/identity/public/cpp:cpp_types",
     "//services/network/public/cpp",
   ]
+
+  deps = [
+    "//components/prefs:prefs",
+  ]
 }
 
 # A source_set for types which the public interfaces depend on for typemapping.
@@ -97,6 +101,7 @@
     "//base",
     "//base/test:test_support",
     "//components/signin/core/browser:internals_test_support",
+    "//components/sync_preferences:test_support",
     "//services/network:test_support",
     "//testing/gtest",
   ]
diff --git a/services/identity/public/cpp/DEPS b/services/identity/public/cpp/DEPS
index 93030af..547d0d50 100644
--- a/services/identity/public/cpp/DEPS
+++ b/services/identity/public/cpp/DEPS
@@ -30,12 +30,12 @@
     "+services/network/test/test_url_loader_factory.h",
     "+services/network/test/test_utils.h",
   ],
+  "accounts_mutator_unittest.cc": [
+    "+services/network/test/test_url_loader_factory.h",
+  ],
   "identity_manager_unittest.cc": [
     "+google_apis/gaia/oauth2_token_service_delegate.h",
     "+services/network/test/test_cookie_manager.h",
     "+services/network/test/test_url_loader_factory.h",
   ],
-  "accounts_mutator_impl_unittest.cc": [
-    "+components/image_fetcher"
-  ]
 }
diff --git a/services/identity/public/cpp/accounts_cookie_mutator_unittest.cc b/services/identity/public/cpp/accounts_cookie_mutator_unittest.cc
index 1339b5c..1279cdf 100644
--- a/services/identity/public/cpp/accounts_cookie_mutator_unittest.cc
+++ b/services/identity/public/cpp/accounts_cookie_mutator_unittest.cc
@@ -19,6 +19,7 @@
 #include "services/identity/public/cpp/accounts_in_cookie_jar_info.h"
 #include "services/identity/public/cpp/identity_manager.h"
 #include "services/identity/public/cpp/identity_test_environment.h"
+#include "services/identity/public/cpp/test_identity_manager_observer.h"
 #include "services/network/test/test_url_loader_factory.h"
 #include "services/network/test/test_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -51,56 +52,6 @@
   kTriggerCookieJarUpdateOneAccount,
 };
 
-// Class that observes updates from identity::IdentityManager.
-class TestIdentityManagerObserver : public identity::IdentityManager::Observer {
- public:
-  explicit TestIdentityManagerObserver(
-      identity::IdentityManager* identity_manager)
-      : identity_manager_(identity_manager) {
-    identity_manager_->AddObserver(this);
-  }
-  ~TestIdentityManagerObserver() override {
-    identity_manager_->RemoveObserver(this);
-  }
-
-  void set_on_accounts_in_cookie_updated_callback(
-      base::OnceCallback<void(const identity::AccountsInCookieJarInfo&,
-                              const GoogleServiceAuthError&)> callback) {
-    on_accounts_in_cookie_updated_callback_ = std::move(callback);
-  }
-
-  void set_on_add_account_to_cookie_completed_callback(
-      base::OnceCallback<void(const std::string&,
-                              const GoogleServiceAuthError&)> callback) {
-    on_add_account_to_cookie_completed_callback_ = std::move(callback);
-  }
-
- private:
-  // identity::IdentityManager::Observer:
-  void OnAccountsInCookieUpdated(
-      const identity::AccountsInCookieJarInfo& accounts_in_cookie_jar_info,
-      const GoogleServiceAuthError& error) override {
-    if (on_accounts_in_cookie_updated_callback_)
-      std::move(on_accounts_in_cookie_updated_callback_)
-          .Run(accounts_in_cookie_jar_info, error);
-  }
-
-  void OnAddAccountToCookieCompleted(
-      const std::string& account_id,
-      const GoogleServiceAuthError& error) override {
-    if (on_add_account_to_cookie_completed_callback_)
-      std::move(on_add_account_to_cookie_completed_callback_)
-          .Run(account_id, error);
-  }
-
-  identity::IdentityManager* identity_manager_;
-  base::OnceCallback<void(const identity::AccountsInCookieJarInfo&,
-                          const GoogleServiceAuthError&)>
-      on_accounts_in_cookie_updated_callback_;
-  base::OnceCallback<void(const std::string&, const GoogleServiceAuthError&)>
-      on_add_account_to_cookie_completed_callback_;
-};
-
 }  // namespace
 
 namespace identity {
@@ -190,22 +141,19 @@
 // results in an error due to such account not being available.
 TEST_F(AccountsCookieMutatorTest, AddAccountToCookie_NonExistingAccount) {
   base::RunLoop run_loop;
-  identity_manager_observer()->set_on_add_account_to_cookie_completed_callback(
-      base::BindOnce(
-          [](base::OnceClosure quit_closure, const std::string& account_id,
-             const GoogleServiceAuthError& error) {
-            EXPECT_EQ(account_id, kTestUnavailableAccountId);
-            // The account was not previously available and no access token was
-            // provided when adding it to the cookie jar: expect an error.
-            EXPECT_EQ(error.state(),
-                      GoogleServiceAuthError::USER_NOT_SIGNED_UP);
-            std::move(quit_closure).Run();
-          },
-          run_loop.QuitClosure()));
-
+  identity_manager_observer()->SetOnAddAccountToCookieCompletedCallback(
+      run_loop.QuitClosure());
   accounts_cookie_mutator()->AddAccountToCookie(kTestUnavailableAccountId,
                                                 gaia::GaiaSource::kChrome);
   run_loop.Run();
+
+  EXPECT_EQ(identity_manager_observer()
+                ->AccountFromAddAccountToCookieCompletedCallback(),
+            kTestUnavailableAccountId);
+  EXPECT_EQ(identity_manager_observer()
+                ->ErrorFromAddAccountToCookieCompletedCallback()
+                .state(),
+            GoogleServiceAuthError::USER_NOT_SIGNED_UP);
 }
 
 // Test that adding an already available account without providing an access
@@ -216,26 +164,22 @@
 
   std::string account_id = AddAcountWithRefreshToken(kTestAccountEmail);
   base::RunLoop run_loop;
-  identity_manager_observer()->set_on_add_account_to_cookie_completed_callback(
-      base::BindOnce(
-          [](base::OnceClosure quit_closure,
-             const std::string& expected_account_id,
-             const std::string& account_id,
-             const GoogleServiceAuthError& error) {
-            EXPECT_EQ(account_id, expected_account_id);
-            // The account was previously available: expect success.
-            EXPECT_EQ(error.state(), GoogleServiceAuthError::NONE);
-            std::move(quit_closure).Run();
-          },
-          run_loop.QuitClosure(), account_id));
-
+  identity_manager_observer()->SetOnAddAccountToCookieCompletedCallback(
+      run_loop.QuitClosure());
   accounts_cookie_mutator()->AddAccountToCookie(account_id,
                                                 gaia::GaiaSource::kChrome);
   identity_test_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
       account_id, kTestAccessToken,
       base::Time::Now() + base::TimeDelta::FromHours(1));
-
   run_loop.Run();
+
+  EXPECT_EQ(identity_manager_observer()
+                ->AccountFromAddAccountToCookieCompletedCallback(),
+            account_id);
+  EXPECT_EQ(identity_manager_observer()
+                ->ErrorFromAddAccountToCookieCompletedCallback()
+                .state(),
+            GoogleServiceAuthError::NONE);
 }
 
 // Test that adding a non existing account along with an access token, results
@@ -246,21 +190,19 @@
       AccountsCookiesMutatorAction::kAddAccountToCookie);
 
   base::RunLoop run_loop;
-  identity_manager_observer()->set_on_add_account_to_cookie_completed_callback(
-      base::BindOnce(
-          [](base::OnceClosure quit_closure, const std::string& account_id,
-             const GoogleServiceAuthError& error) {
-            EXPECT_EQ(account_id, kTestUnavailableAccountId);
-            // Trying to add an non-available account with a valid token to the
-            // cookie jar should result in the account being merged.
-            EXPECT_EQ(error.state(), GoogleServiceAuthError::NONE);
-            std::move(quit_closure).Run();
-          },
-          run_loop.QuitClosure()));
-
+  identity_manager_observer()->SetOnAddAccountToCookieCompletedCallback(
+      run_loop.QuitClosure());
   accounts_cookie_mutator()->AddAccountToCookieWithToken(
       kTestUnavailableAccountId, kTestAccessToken, gaia::GaiaSource::kChrome);
   run_loop.Run();
+
+  EXPECT_EQ(identity_manager_observer()
+                ->AccountFromAddAccountToCookieCompletedCallback(),
+            kTestUnavailableAccountId);
+  EXPECT_EQ(identity_manager_observer()
+                ->ErrorFromAddAccountToCookieCompletedCallback()
+                .state(),
+            GoogleServiceAuthError::NONE);
 }
 
 // Test that adding an already available account along with an access token,
@@ -272,24 +214,20 @@
 
   std::string account_id = AddAcountWithRefreshToken(kTestAccountEmail);
   base::RunLoop run_loop;
-  identity_manager_observer()->set_on_add_account_to_cookie_completed_callback(
-      base::BindOnce(
-          [](base::OnceClosure quit_closure,
-             const std::string& expected_account_id,
-             const std::string& account_id,
-             const GoogleServiceAuthError& error) {
-            EXPECT_EQ(account_id, expected_account_id);
-            // Trying to add a previously available account with a valid token
-            // to the cookie jar should also result in the account being merged.
-            EXPECT_EQ(error.state(), GoogleServiceAuthError::NONE);
-            std::move(quit_closure).Run();
-          },
-          run_loop.QuitClosure(), account_id));
-
+  identity_manager_observer()->SetOnAddAccountToCookieCompletedCallback(
+      run_loop.QuitClosure());
   accounts_cookie_mutator()->AddAccountToCookieWithToken(
       account_id, kTestAccessToken, gaia::GaiaSource::kChrome);
 
   run_loop.Run();
+
+  EXPECT_EQ(identity_manager_observer()
+                ->AccountFromAddAccountToCookieCompletedCallback(),
+            account_id);
+  EXPECT_EQ(identity_manager_observer()
+                ->ErrorFromAddAccountToCookieCompletedCallback()
+                .state(),
+            GoogleServiceAuthError::NONE);
 }
 
 // Test that trying to set a list of accounts in the cookie jar where none of
@@ -376,21 +314,22 @@
       AccountsCookiesMutatorAction::kTriggerCookieJarUpdateNoAccounts);
 
   base::RunLoop run_loop;
-  identity_manager_observer()->set_on_accounts_in_cookie_updated_callback(
-      base::BindOnce(
-          [](base::OnceClosure quit_closure,
-             const identity::AccountsInCookieJarInfo& accounts_in_jar_info,
-             const GoogleServiceAuthError& error) {
-            EXPECT_EQ(accounts_in_jar_info.signed_in_accounts.size(), 0U);
-            EXPECT_EQ(accounts_in_jar_info.signed_out_accounts.size(), 0U);
-            EXPECT_TRUE(accounts_in_jar_info.accounts_are_fresh);
-            EXPECT_EQ(error.state(), GoogleServiceAuthError::NONE);
-            std::move(quit_closure).Run();
-          },
-          run_loop.QuitClosure()));
-
+  identity_manager_observer()->SetOnAccountsInCookieUpdatedCallback(
+      run_loop.QuitClosure());
   accounts_cookie_mutator()->TriggerCookieJarUpdate();
   run_loop.Run();
+
+  const AccountsInCookieJarInfo& accounts_in_jar_info =
+      identity_manager_observer()
+          ->AccountsInfoFromAccountsInCookieUpdatedCallback();
+  EXPECT_EQ(accounts_in_jar_info.signed_in_accounts.size(), 0U);
+  EXPECT_EQ(accounts_in_jar_info.signed_out_accounts.size(), 0U);
+  EXPECT_TRUE(accounts_in_jar_info.accounts_are_fresh);
+
+  EXPECT_EQ(identity_manager_observer()
+                ->ErrorFromAddAccountToCookieCompletedCallback()
+                .state(),
+            GoogleServiceAuthError::NONE);
 }
 
 // Test triggering the update of a cookie jar with one accounts works and that
@@ -400,46 +339,47 @@
       AccountsCookiesMutatorAction::kTriggerCookieJarUpdateOneAccount);
 
   base::RunLoop run_loop;
-  identity_manager_observer()->set_on_accounts_in_cookie_updated_callback(
-      base::BindOnce(
-          [](base::OnceClosure quit_closure,
-             const identity::AccountsInCookieJarInfo& accounts_in_jar_info,
-             const GoogleServiceAuthError& error) {
-            EXPECT_EQ(accounts_in_jar_info.signed_in_accounts.size(), 1U);
-            EXPECT_EQ(accounts_in_jar_info.signed_in_accounts[0].gaia_id,
-                      kTestAccountGaiaId);
-            EXPECT_EQ(accounts_in_jar_info.signed_in_accounts[0].email,
-                      kTestAccountEmail);
-
-            EXPECT_EQ(accounts_in_jar_info.signed_out_accounts.size(), 0U);
-            EXPECT_TRUE(accounts_in_jar_info.accounts_are_fresh);
-            EXPECT_EQ(error.state(), GoogleServiceAuthError::NONE);
-            std::move(quit_closure).Run();
-          },
-          run_loop.QuitClosure()));
-
+  identity_manager_observer()->SetOnAccountsInCookieUpdatedCallback(
+      run_loop.QuitClosure());
   accounts_cookie_mutator()->TriggerCookieJarUpdate();
   run_loop.Run();
+
+  const AccountsInCookieJarInfo& accounts_in_jar_info =
+      identity_manager_observer()
+          ->AccountsInfoFromAccountsInCookieUpdatedCallback();
+  EXPECT_EQ(accounts_in_jar_info.signed_in_accounts.size(), 1U);
+  EXPECT_EQ(accounts_in_jar_info.signed_in_accounts[0].gaia_id,
+            kTestAccountGaiaId);
+  EXPECT_EQ(accounts_in_jar_info.signed_in_accounts[0].email,
+            kTestAccountEmail);
+
+  EXPECT_EQ(accounts_in_jar_info.signed_out_accounts.size(), 0U);
+  EXPECT_TRUE(accounts_in_jar_info.accounts_are_fresh);
+
+  EXPECT_EQ(identity_manager_observer()
+                ->ErrorFromAddAccountToCookieCompletedCallback()
+                .state(),
+            GoogleServiceAuthError::NONE);
 }
 
 // Test that calling ForceTriggerOnAddAccountToCookieCompleted() with an account
 // ID and a valid error runs the callback with that data passed through.
 TEST_F(AccountsCookieMutatorTest, ForceTriggerOnAddAccountToCookieCompleted) {
   base::RunLoop run_loop;
-  identity_manager_observer()->set_on_add_account_to_cookie_completed_callback(
-      base::BindOnce(
-          [](base::OnceClosure quit_closure, const std::string& account_id,
-             const GoogleServiceAuthError& error) {
-            EXPECT_EQ(account_id, kTestUnavailableAccountId);
-            EXPECT_EQ(error.state(), GoogleServiceAuthError::TWO_FACTOR);
-            std::move(quit_closure).Run();
-          },
-          run_loop.QuitClosure()));
-
+  identity_manager_observer()->SetOnAddAccountToCookieCompletedCallback(
+      run_loop.QuitClosure());
   accounts_cookie_mutator()->ForceTriggerOnAddAccountToCookieCompleted(
       kTestUnavailableAccountId,
       GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR));
   run_loop.Run();
+
+  EXPECT_EQ(identity_manager_observer()
+                ->AccountFromAddAccountToCookieCompletedCallback(),
+            kTestUnavailableAccountId);
+  EXPECT_EQ(identity_manager_observer()
+                ->ErrorFromAddAccountToCookieCompletedCallback()
+                .state(),
+            GoogleServiceAuthError::TWO_FACTOR);
 }
 
 }  // namespace identity
diff --git a/services/identity/public/cpp/accounts_mutator.h b/services/identity/public/cpp/accounts_mutator.h
index 414e143c..047b4e58 100644
--- a/services/identity/public/cpp/accounts_mutator.h
+++ b/services/identity/public/cpp/accounts_mutator.h
@@ -56,7 +56,9 @@
   // Removes the credentials associated to account_id from the internal storage,
   // and moves them to |target|. The credentials are not revoked on the server,
   // but the IdentityManager::Observer::OnRefreshTokenRemovedForAccount()
-  // notification is sent to the observers.
+  // notification is sent to the observers. Also recreates a new device ID for
+  // this mutator. The device ID of the current mutator is not moved to the
+  // target mutator.
   virtual void MoveAccount(AccountsMutator* target,
                            const std::string& account_id) = 0;
 #endif
diff --git a/services/identity/public/cpp/accounts_mutator_impl.cc b/services/identity/public/cpp/accounts_mutator_impl.cc
index b5808ad..5ededbc 100644
--- a/services/identity/public/cpp/accounts_mutator_impl.cc
+++ b/services/identity/public/cpp/accounts_mutator_impl.cc
@@ -7,8 +7,10 @@
 #include <string>
 
 #include "base/logging.h"
+#include "components/prefs/pref_service.h"
 #include "components/signin/core/browser/account_info.h"
 #include "components/signin/core/browser/account_tracker_service.h"
+#include "components/signin/core/browser/device_id_helper.h"
 #include "components/signin/core/browser/profile_oauth2_token_service.h"
 #include "components/signin/core/browser/signin_manager_base.h"
 
@@ -17,13 +19,19 @@
 AccountsMutatorImpl::AccountsMutatorImpl(
     ProfileOAuth2TokenService* token_service,
     AccountTrackerService* account_tracker_service,
-    SigninManagerBase* signin_manager)
+    SigninManagerBase* signin_manager,
+    PrefService* pref_service)
     : token_service_(token_service),
       account_tracker_service_(account_tracker_service),
       signin_manager_(signin_manager) {
   DCHECK(token_service_);
   DCHECK(account_tracker_service_);
   DCHECK(signin_manager_);
+#if BUILDFLAG(ENABLE_DICE_SUPPORT)
+  // TODO(myid.shin): Ensure a non-null PrefService is passed in tests and add
+  // DCHECK here.
+  pref_service_ = pref_service;
+#endif
 }
 
 AccountsMutatorImpl::~AccountsMutatorImpl() {}
@@ -89,6 +97,13 @@
   auto* target_impl = static_cast<AccountsMutatorImpl*>(target);
   target_impl->account_tracker_service_->SeedAccountInfo(account_info);
   token_service_->ExtractCredentials(target_impl->token_service_, account_id);
+
+  DCHECK(pref_service_);
+  // Reset the device ID from the source mutator: the exported token is linked
+  // to the device ID of the current mutator on the server. Reset the device ID
+  // of the current mutator to avoid tying it with the new mutator. See
+  // https://crbug.com/813928#c16
+  signin::RecreateSigninScopedDeviceId(pref_service_);
 }
 #endif
 
diff --git a/services/identity/public/cpp/accounts_mutator_impl.h b/services/identity/public/cpp/accounts_mutator_impl.h
index 728f1a18..4ba626fc 100644
--- a/services/identity/public/cpp/accounts_mutator_impl.h
+++ b/services/identity/public/cpp/accounts_mutator_impl.h
@@ -8,10 +8,13 @@
 #include <string>
 
 #include "base/macros.h"
+#include "build/buildflag.h"
+#include "components/signin/core/browser/signin_buildflags.h"
 #include "components/signin/core/browser/signin_metrics.h"
 #include "services/identity/public/cpp/accounts_mutator.h"
 
 class AccountTrackerService;
+class PrefService;
 class ProfileOAuth2TokenService;
 class SigninManagerBase;
 
@@ -22,7 +25,8 @@
  public:
   explicit AccountsMutatorImpl(ProfileOAuth2TokenService* token_service,
                                AccountTrackerService* account_tracker_service,
-                               SigninManagerBase* signin_manager);
+                               SigninManagerBase* signin_manager,
+                               PrefService* pref_service);
   ~AccountsMutatorImpl() override;
 
   // AccountsMutator:
@@ -56,7 +60,9 @@
   ProfileOAuth2TokenService* token_service_;
   AccountTrackerService* account_tracker_service_;
   SigninManagerBase* signin_manager_;
-
+#if BUILDFLAG(ENABLE_DICE_SUPPORT)
+  PrefService* pref_service_;
+#endif
   DISALLOW_COPY_AND_ASSIGN(AccountsMutatorImpl);
 };
 
diff --git a/services/identity/public/cpp/accounts_mutator_unittest.cc b/services/identity/public/cpp/accounts_mutator_unittest.cc
index dea93819..fd5b9983 100644
--- a/services/identity/public/cpp/accounts_mutator_unittest.cc
+++ b/services/identity/public/cpp/accounts_mutator_unittest.cc
@@ -8,11 +8,15 @@
 #include "base/message_loop/message_loop.h"
 #include "base/optional.h"
 #include "base/test/gtest_util.h"
+#include "components/signin/core/browser/device_id_helper.h"
 #include "components/signin/core/browser/signin_metrics.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "services/identity/public/cpp/accounts_mutator_impl.h"
 #include "services/identity/public/cpp/identity_manager.h"
 #include "services/identity/public/cpp/identity_test_environment.h"
 #include "services/identity/public/cpp/identity_test_utils.h"
+#include "services/identity/public/cpp/test_identity_manager_observer.h"
+#include "services/network/test/test_url_loader_factory.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace {
@@ -25,49 +29,6 @@
 const char kRefreshToken2[] = "refresh_token_2";
 const char kSupervisedUserPseudoEmail[] = "managed_user@localhost";
 
-// Class that observes updates from identity::IdentityManager.
-class TestIdentityManagerObserver : public identity::IdentityManager::Observer {
- public:
-  explicit TestIdentityManagerObserver(
-      identity::IdentityManager* identity_manager)
-      : identity_manager_(identity_manager) {
-    identity_manager_->AddObserver(this);
-  }
-  ~TestIdentityManagerObserver() override {
-    identity_manager_->RemoveObserver(this);
-  }
-
-  void set_on_refresh_token_updated_callback(
-      base::OnceCallback<void(const std::string&)> callback) {
-    on_refresh_token_updated_callback_ = std::move(callback);
-  }
-
-  void set_on_refresh_token_removed_callback(
-      base::OnceCallback<void(const std::string&)> callback) {
-    on_refresh_token_removed_callback_ = std::move(callback);
-  }
-
- private:
-  // identity::IdentityManager::Observer:
-  void OnRefreshTokenUpdatedForAccount(
-      const CoreAccountInfo& account_info) override {
-    if (on_refresh_token_updated_callback_)
-      std::move(on_refresh_token_updated_callback_)
-          .Run(account_info.account_id);
-  }
-
-  void OnRefreshTokenRemovedForAccount(const std::string& account_id) override {
-    if (on_refresh_token_removed_callback_)
-      std::move(on_refresh_token_removed_callback_).Run(account_id);
-  }
-
-  identity::IdentityManager* identity_manager_;
-  base::OnceCallback<void(const std::string&)>
-      on_refresh_token_updated_callback_;
-  base::OnceCallback<void(const std::string&)>
-      on_refresh_token_removed_callback_;
-};
-
 // Class that observes diagnostics updates from identity::IdentityManager.
 class TestIdentityManagerDiagnosticsObserver
     : public identity::IdentityManager::DiagnosticsObserver {
@@ -125,11 +86,14 @@
 class AccountsMutatorTest : public testing::Test {
  public:
   AccountsMutatorTest()
-      : identity_manager_observer_(identity_manager()),
+      : identity_test_env_(&test_url_loader_factory_, &prefs_),
+        identity_manager_observer_(identity_manager()),
         identity_manager_diagnostics_observer_(identity_manager()) {}
 
   ~AccountsMutatorTest() override {}
 
+  PrefService* pref_service() { return &prefs_; }
+
   identity::IdentityManager* identity_manager() {
     return identity_test_env_.identity_manager();
   }
@@ -149,6 +113,8 @@
 
  private:
   base::MessageLoop message_loop_;
+  sync_preferences::TestingPrefServiceSyncable prefs_;
+  network::TestURLLoaderFactory test_url_loader_factory_;
   identity::IdentityTestEnvironment identity_test_env_;
   TestIdentityManagerObserver identity_manager_observer_;
   TestIdentityManagerDiagnosticsObserver identity_manager_diagnostics_observer_;
@@ -169,12 +135,8 @@
     return;
 
   base::RunLoop run_loop;
-  identity_manager_observer()->set_on_refresh_token_updated_callback(
-      base::BindOnce(
-          [](base::OnceClosure quit_closure, const std::string& account_id) {
-            std::move(quit_closure).Run();
-          },
-          run_loop.QuitClosure()));
+  identity_manager_observer()->SetOnRefreshTokenUpdatedCallback(
+      run_loop.QuitClosure());
 
   std::string account_id = accounts_mutator()->AddOrUpdateAccount(
       kTestGaiaId, kTestEmail, kRefreshToken,
@@ -206,12 +168,8 @@
 
   // First of all add the account to the account tracker service.
   base::RunLoop run_loop;
-  identity_manager_observer()->set_on_refresh_token_updated_callback(
-      base::BindOnce(
-          [](base::OnceClosure quit_closure, const std::string& account_id) {
-            std::move(quit_closure).Run();
-          },
-          run_loop.QuitClosure()));
+  identity_manager_observer()->SetOnRefreshTokenUpdatedCallback(
+      run_loop.QuitClosure());
 
   std::string account_id = accounts_mutator()->AddOrUpdateAccount(
       kTestGaiaId, kTestEmail, kRefreshToken,
@@ -234,15 +192,8 @@
   // Now try adding the account again with the same account id but with
   // different information, and check that the account gets updated.
   base::RunLoop run_loop2;
-  identity_manager_observer()->set_on_refresh_token_updated_callback(
-      base::BindOnce(
-          [](base::OnceClosure quit_closure,
-             const std::string& expected_account_id,
-             const std::string& added_account_id) {
-            EXPECT_EQ(added_account_id, expected_account_id);
-            std::move(quit_closure).Run();
-          },
-          run_loop2.QuitClosure(), account_id));
+  identity_manager_observer()->SetOnRefreshTokenUpdatedCallback(
+      run_loop2.QuitClosure());
 
   // The internals of IdentityService is migrating from email to gaia id
   // as the account id. Detect whether the current plaform has completed
@@ -261,6 +212,11 @@
       signin_metrics::SourceForRefreshTokenOperation::kUnknown);
   run_loop2.Run();
 
+  EXPECT_EQ(identity_manager_observer()
+                ->AccountFromRefreshTokenUpdatedCallback()
+                .account_id,
+            account_id);
+
   // No new accounts should be created, just the information should be updated.
   EXPECT_EQ(identity_manager()->GetAccountsWithRefreshTokens().size(), 1U);
   AccountInfo updated_account_info =
@@ -286,12 +242,8 @@
 
   // First of all add the account to the account tracker service.
   base::RunLoop run_loop;
-  identity_manager_observer()->set_on_refresh_token_updated_callback(
-      base::BindOnce(
-          [](base::OnceClosure quit_closure, const std::string& account_id) {
-            std::move(quit_closure).Run();
-          },
-          run_loop.QuitClosure()));
+  identity_manager_observer()->SetOnRefreshTokenUpdatedCallback(
+      run_loop.QuitClosure());
 
   std::string account_id = accounts_mutator()->AddOrUpdateAccount(
       kTestGaiaId, kTestEmail, kRefreshToken,
@@ -379,20 +331,17 @@
 
   // Now try invalidating the primary account, and check that it gets updated.
   base::RunLoop run_loop;
-  identity_manager_observer()->set_on_refresh_token_updated_callback(
-      base::BindOnce(
-          [](base::OnceClosure quit_closure,
-             const std::string& expected_account_id,
-             const std::string& added_or_updated_account_id) {
-            EXPECT_EQ(added_or_updated_account_id, expected_account_id);
-            std::move(quit_closure).Run();
-          },
-          run_loop.QuitClosure(), primary_account_info.account_id));
+  identity_manager_observer()->SetOnRefreshTokenUpdatedCallback(
+      run_loop.QuitClosure());
 
   accounts_mutator()->InvalidateRefreshTokenForPrimaryAccount(
       signin_metrics::SourceForRefreshTokenOperation::kUnknown);
   run_loop.Run();
 
+  EXPECT_EQ(identity_manager_observer()
+                ->AccountFromRefreshTokenUpdatedCallback()
+                .account_id,
+            primary_account_info.account_id);
   EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(
       primary_account_info.account_id));
   EXPECT_TRUE(
@@ -420,12 +369,8 @@
 
   // Next, add a secondary account.
   base::RunLoop run_loop;
-  identity_manager_observer()->set_on_refresh_token_updated_callback(
-      base::BindOnce(
-          [](base::OnceClosure quit_closure, const std::string& account_id) {
-            std::move(quit_closure).Run();
-          },
-          run_loop.QuitClosure()));
+  identity_manager_observer()->SetOnRefreshTokenUpdatedCallback(
+      run_loop.QuitClosure());
 
   std::string account_id = accounts_mutator()->AddOrUpdateAccount(
       kTestGaiaId, kTestEmail, kRefreshToken,
@@ -441,20 +386,18 @@
 
   // Now try invalidating the primary account, and check that it gets updated.
   base::RunLoop run_loop2;
-  identity_manager_observer()->set_on_refresh_token_updated_callback(
-      base::BindOnce(
-          [](base::OnceClosure quit_closure,
-             const std::string& expected_account_id,
-             const std::string& added_or_updated_account_id) {
-            EXPECT_EQ(added_or_updated_account_id, expected_account_id);
-            std::move(quit_closure).Run();
-          },
-          run_loop2.QuitClosure(), primary_account_info.account_id));
+  identity_manager_observer()->SetOnRefreshTokenUpdatedCallback(
+      run_loop2.QuitClosure());
 
   accounts_mutator()->InvalidateRefreshTokenForPrimaryAccount(
       signin_metrics::SourceForRefreshTokenOperation::kUnknown);
   run_loop2.Run();
 
+  EXPECT_EQ(identity_manager_observer()
+                ->AccountFromRefreshTokenUpdatedCallback()
+                .account_id,
+            primary_account_info.account_id);
+
   // Check whether the primary account refresh token got invalidated.
   EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(
       primary_account_info.account_id));
@@ -503,8 +446,8 @@
     return;
 
   base::RunLoop run_loop;
-  identity_manager_observer()->set_on_refresh_token_updated_callback(
-      base::BindOnce([](const std::string& account_id) {
+  identity_manager_observer()->SetOnRefreshTokenUpdatedCallback(
+      base::BindOnce([]() {
         // This callback should not be invoked now.
         EXPECT_TRUE(false);
       }));
@@ -529,12 +472,8 @@
 
   // First of all add the account to the account tracker service.
   base::RunLoop run_loop;
-  identity_manager_observer()->set_on_refresh_token_updated_callback(
-      base::BindOnce(
-          [](base::OnceClosure quit_closure, const std::string& account_id) {
-            std::move(quit_closure).Run();
-          },
-          run_loop.QuitClosure()));
+  identity_manager_observer()->SetOnRefreshTokenUpdatedCallback(
+      run_loop.QuitClosure());
 
   std::string account_id = accounts_mutator()->AddOrUpdateAccount(
       kTestGaiaId, kTestEmail, kRefreshToken,
@@ -550,20 +489,17 @@
 
   // Now remove the account that we just added.
   base::RunLoop run_loop2;
-  identity_manager_observer()->set_on_refresh_token_removed_callback(
-      base::BindOnce(
-          [](base::OnceClosure quit_closure,
-             const std::string& expected_account_id,
-             const std::string& removed_account_id) {
-            EXPECT_EQ(removed_account_id, expected_account_id);
-            std::move(quit_closure).Run();
-          },
-          run_loop2.QuitClosure(), account_id));
+  identity_manager_observer()->SetOnRefreshTokenRemovedCallback(
+      run_loop2.QuitClosure());
 
   accounts_mutator()->RemoveAccount(
       account_id, signin_metrics::SourceForRefreshTokenOperation::kUnknown);
   run_loop2.Run();
 
+  EXPECT_EQ(
+      identity_manager_observer()->AccountIdFromRefreshTokenRemovedCallback(),
+      account_id);
+
   EXPECT_FALSE(identity_manager()->HasAccountWithRefreshToken(account_id));
   EXPECT_FALSE(
       identity_manager()->HasAccountWithRefreshTokenInPersistentErrorState(
@@ -580,12 +516,8 @@
 
   // First of all the first account to the account tracker service.
   base::RunLoop run_loop;
-  identity_manager_observer()->set_on_refresh_token_updated_callback(
-      base::BindOnce(
-          [](base::OnceClosure quit_closure, const std::string& account_id) {
-            std::move(quit_closure).Run();
-          },
-          run_loop.QuitClosure()));
+  identity_manager_observer()->SetOnRefreshTokenUpdatedCallback(
+      run_loop.QuitClosure());
 
   std::string account_id = accounts_mutator()->AddOrUpdateAccount(
       kTestGaiaId, kTestEmail, kRefreshToken,
@@ -601,12 +533,8 @@
 
   // Now add the second account.
   base::RunLoop run_loop2;
-  identity_manager_observer()->set_on_refresh_token_updated_callback(
-      base::BindOnce(
-          [](base::OnceClosure quit_closure, const std::string& account_id) {
-            std::move(quit_closure).Run();
-          },
-          run_loop2.QuitClosure()));
+  identity_manager_observer()->SetOnRefreshTokenUpdatedCallback(
+      run_loop2.QuitClosure());
 
   std::string account_id2 = accounts_mutator()->AddOrUpdateAccount(
       kTestGaiaId2, kTestEmail2, kRefreshToken2,
@@ -650,10 +578,19 @@
   auto* other_accounts_mutator =
       other_identity_test_env.identity_manager()->GetAccountsMutator();
 
+  std::string device_id_1 = signin::GetOrCreateScopedDeviceId(pref_service());
+  EXPECT_FALSE(device_id_1.empty());
+
   accounts_mutator()->MoveAccount(other_accounts_mutator,
                                   account_info.account_id);
   EXPECT_EQ(0U, identity_manager()->GetAccountsWithRefreshTokens().size());
 
+  std::string device_id_2 = signin::GetOrCreateScopedDeviceId(pref_service());
+  EXPECT_FALSE(device_id_2.empty());
+  // |device_id_1| and |device_id_2| should be different as the divice ID is
+  // recreated in MoveAccount().
+  EXPECT_NE(device_id_1, device_id_2);
+
   auto other_accounts_with_refresh_token =
       other_identity_test_env.identity_manager()
           ->GetAccountsWithRefreshTokens();
@@ -675,12 +612,8 @@
   EXPECT_EQ(identity_manager()->GetAccountsWithRefreshTokens().size(), 0U);
 
   base::RunLoop run_loop;
-  identity_manager_observer()->set_on_refresh_token_updated_callback(
-      base::BindOnce(
-          [](base::OnceClosure quit_closure, const std::string& account_id) {
-            std::move(quit_closure).Run();
-          },
-          run_loop.QuitClosure()));
+  identity_manager_observer()->SetOnRefreshTokenUpdatedCallback(
+      run_loop.QuitClosure());
 
   accounts_mutator()->LegacySetRefreshTokenForSupervisedUser(kRefreshToken);
   run_loop.Run();
diff --git a/services/identity/public/cpp/identity_manager.cc b/services/identity/public/cpp/identity_manager.cc
index af9fff8..5de99d4 100644
--- a/services/identity/public/cpp/identity_manager.cc
+++ b/services/identity/public/cpp/identity_manager.cc
@@ -285,6 +285,11 @@
 void IdentityManager::ForceTriggerOnCookieChange() {
   gaia_cookie_manager_service_->ForceOnCookieChangeProcessing();
 }
+
+void IdentityManager::LegacyAddAccountFromSystem(
+    const std::string& account_id) {
+  token_service_->GetDelegate()->AddAccountFromSystem(account_id);
+}
 #endif
 
 #if defined(OS_ANDROID) || defined(OS_IOS)
diff --git a/services/identity/public/cpp/identity_manager.h b/services/identity/public/cpp/identity_manager.h
index 3f43070..f7b097b5 100644
--- a/services/identity/public/cpp/identity_manager.h
+++ b/services/identity/public/cpp/identity_manager.h
@@ -398,6 +398,13 @@
   // TODO(https://crbug.com/930582) : Remove the need to expose this method
   // or move it to the network::CookieManager.
   void ForceTriggerOnCookieChange();
+
+  // Adds a given account to the token service from a system account. This
+  // API calls OAuth2TokenServiceDelegate::AddAccountFromSystem and it
+  // triggers platform specific implementation for IOS.
+  // NOTE: In normal usage, this method SHOULD NOT be called.
+  // TODO(https://crbug.com/930094): Eliminate the need to expose this.
+  void LegacyAddAccountFromSystem(const std::string& account_id);
 #endif
 
 #if defined(OS_ANDROID) || defined(OS_IOS)
diff --git a/services/identity/public/cpp/identity_manager_unittest.cc b/services/identity/public/cpp/identity_manager_unittest.cc
index ca6caa8..f05f497 100644
--- a/services/identity/public/cpp/identity_manager_unittest.cc
+++ b/services/identity/public/cpp/identity_manager_unittest.cc
@@ -131,24 +131,28 @@
     on_google_signed_out_callback_ = std::move(callback);
   }
 
-  const AccountInfo& primary_account_from_signin_callback() const {
+  const CoreAccountInfo& primary_account_from_signin_callback() const {
     return primary_account_from_signin_callback_;
   }
-  const AccountInfo& primary_account_from_signout_callback() const {
+  const CoreAccountInfo& primary_account_from_signout_callback() const {
     return primary_account_from_signout_callback_;
   }
 
  private:
   // SigninManager::Observer:
-  void GoogleSigninSucceeded(const AccountInfo& account_info) override {
-    ASSERT_TRUE(identity_manager_);
+  void GoogleSigninSucceeded(const AccountInfo&) override {
+    // Fetch the primary account from IdentityManager. The goal is to check
+    // that the account from IdentityManager has correct values even if other
+    // SigninManager::Observer are notified.
     primary_account_from_signin_callback_ =
         identity_manager_->GetPrimaryAccountInfo();
     if (on_google_signin_succeeded_callback_)
       std::move(on_google_signin_succeeded_callback_).Run();
   }
-  void GoogleSignedOut(const AccountInfo& account_info) override {
-    ASSERT_TRUE(identity_manager_);
+  void GoogleSignedOut(const AccountInfo&) override {
+    // Fetch the primary account from IdentityManager. The goal is to check
+    // that the account from IdentityManager has correct values even if other
+    // SigninManager::Observer are notified.
     primary_account_from_signout_callback_ =
         identity_manager_->GetPrimaryAccountInfo();
     if (on_google_signed_out_callback_)
@@ -160,8 +164,8 @@
   base::OnceClosure on_google_signin_succeeded_callback_;
   base::OnceClosure on_google_signin_failed_callback_;
   base::OnceClosure on_google_signed_out_callback_;
-  AccountInfo primary_account_from_signin_callback_;
-  AccountInfo primary_account_from_signout_callback_;
+  CoreAccountInfo primary_account_from_signin_callback_;
+  CoreAccountInfo primary_account_from_signout_callback_;
 };
 
 // Class that observes updates from ProfileOAuth2TokenService and and verifies
@@ -436,7 +440,7 @@
 
 // Test that IdentityManager starts off with the information in SigninManager.
 TEST_F(IdentityManagerTest, PrimaryAccountInfoAtStartup) {
-  AccountInfo primary_account_info =
+  CoreAccountInfo primary_account_info =
       identity_manager()->GetPrimaryAccountInfo();
   EXPECT_EQ(kTestGaiaId, primary_account_info.gaia);
   EXPECT_EQ(kTestEmail, primary_account_info.email);
@@ -462,7 +466,7 @@
   EXPECT_EQ(kTestGaiaId, primary_account_from_set_callback.gaia);
   EXPECT_EQ(kTestEmail, primary_account_from_set_callback.email);
 
-  AccountInfo primary_account_info =
+  CoreAccountInfo primary_account_info =
       identity_manager()->GetPrimaryAccountInfo();
   EXPECT_EQ(kTestGaiaId, primary_account_info.gaia);
   EXPECT_EQ(kTestEmail, primary_account_info.email);
@@ -498,7 +502,7 @@
   EXPECT_EQ(kTestGaiaId, primary_account_from_cleared_callback.gaia);
   EXPECT_EQ(kTestEmail, primary_account_from_cleared_callback.email);
 
-  AccountInfo primary_account_info =
+  CoreAccountInfo primary_account_info =
       identity_manager()->GetPrimaryAccountInfo();
   EXPECT_EQ("", primary_account_info.gaia);
   EXPECT_EQ("", primary_account_info.email);
@@ -525,7 +529,7 @@
   // the IdentityManager is still storing the primary account's ID.
   account_tracker()->RemoveAccount(kTestGaiaId);
 
-  AccountInfo primary_account_info =
+  CoreAccountInfo primary_account_info =
       identity_manager()->GetPrimaryAccountInfo();
   EXPECT_EQ("", primary_account_info.gaia);
   EXPECT_EQ("", primary_account_info.email);
@@ -595,7 +599,7 @@
 
 TEST_F(IdentityManagerTest,
        QueryingOfRefreshTokensInteractionWithPrimaryAccount) {
-  AccountInfo account_info = identity_manager()->GetPrimaryAccountInfo();
+  CoreAccountInfo account_info = identity_manager()->GetPrimaryAccountInfo();
   std::string account_id = account_info.account_id;
 
   // Should not have a refresh token for the primary account at initialization.
@@ -658,7 +662,7 @@
 
 TEST_F(IdentityManagerTest,
        QueryingOfRefreshTokensReflectsNonemptyInitialState) {
-  AccountInfo account_info = identity_manager()->GetPrimaryAccountInfo();
+  CoreAccountInfo account_info = identity_manager()->GetPrimaryAccountInfo();
   std::string account_id = account_info.account_id;
 
   EXPECT_FALSE(
@@ -901,7 +905,7 @@
 TEST_F(
     IdentityManagerTest,
     HasAccountWithRefreshTokenInteractionBetweenPrimaryAndSecondaryAccounts) {
-  AccountInfo primary_account_info =
+  CoreAccountInfo primary_account_info =
       identity_manager()->GetPrimaryAccountInfo();
   std::string primary_account_id = primary_account_info.account_id;
 
@@ -943,7 +947,7 @@
 
 TEST_F(IdentityManagerTest,
        CallbackSentOnUpdateToErrorStateOfRefreshTokenForAccount) {
-  AccountInfo primary_account_info =
+  CoreAccountInfo primary_account_info =
       identity_manager()->GetPrimaryAccountInfo();
   std::string primary_account_id = primary_account_info.account_id;
   SetRefreshTokenForPrimaryAccount(identity_manager());
@@ -997,7 +1001,7 @@
 }
 
 TEST_F(IdentityManagerTest, GetErrorStateOfRefreshTokenForAccount) {
-  AccountInfo primary_account_info =
+  CoreAccountInfo primary_account_info =
       identity_manager()->GetPrimaryAccountInfo();
   std::string primary_account_id = primary_account_info.account_id;
 
@@ -1338,7 +1342,7 @@
   signin_manager()->SignIn(kTestGaiaId, kTestEmail);
   run_loop.Run();
 
-  AccountInfo primary_account_from_signin_callback =
+  CoreAccountInfo primary_account_from_signin_callback =
       signin_manager_observer.primary_account_from_signin_callback();
   EXPECT_EQ(kTestGaiaId, primary_account_from_signin_callback.gaia);
   EXPECT_EQ(kTestEmail, primary_account_from_signin_callback.email);
@@ -1363,7 +1367,7 @@
   signin_manager()->ForceSignOut();
   run_loop.Run();
 
-  AccountInfo primary_account_from_signout_callback =
+  CoreAccountInfo primary_account_from_signout_callback =
       signin_manager_observer.primary_account_from_signout_callback();
   EXPECT_EQ(std::string(), primary_account_from_signout_callback.gaia);
   EXPECT_EQ(std::string(), primary_account_from_signout_callback.email);
@@ -1380,7 +1384,7 @@
 // IdentityManager correctly reflects the updated version. See crbug.com/842041
 // and crbug.com/842670 for further details.
 TEST_F(IdentityManagerTest, IdentityManagerReflectsUpdatedEmailAddress) {
-  AccountInfo primary_account_info =
+  CoreAccountInfo primary_account_info =
       identity_manager()->GetPrimaryAccountInfo();
   EXPECT_EQ(kTestGaiaId, primary_account_info.gaia);
   EXPECT_EQ(kTestEmail, primary_account_info.email);
@@ -1433,7 +1437,7 @@
 
   EXPECT_EQ(
       account_id,
-      identity_manager_observer()->AccountFromRefreshTokenRemovedCallback());
+      identity_manager_observer()->AccountIdFromRefreshTokenRemovedCallback());
 }
 
 TEST_F(IdentityManagerTest,
@@ -1475,7 +1479,7 @@
 
   EXPECT_EQ(
       expected_account_info.account_id,
-      identity_manager_observer()->AccountFromRefreshTokenRemovedCallback());
+      identity_manager_observer()->AccountIdFromRefreshTokenRemovedCallback());
 }
 
 #if !defined(OS_CHROMEOS)
@@ -1539,7 +1543,7 @@
 
   EXPECT_EQ(
       expected_account_info.account_id,
-      identity_manager_observer()->AccountFromRefreshTokenRemovedCallback());
+      identity_manager_observer()->AccountIdFromRefreshTokenRemovedCallback());
 }
 #endif
 
@@ -1559,7 +1563,7 @@
 
   EXPECT_EQ(
       dummy_account_id,
-      identity_manager_observer()->AccountFromRefreshTokenRemovedCallback());
+      identity_manager_observer()->AccountIdFromRefreshTokenRemovedCallback());
 }
 
 TEST_F(
diff --git a/services/identity/public/cpp/identity_test_environment.cc b/services/identity/public/cpp/identity_test_environment.cc
index 040bb85..914295c 100644
--- a/services/identity/public/cpp/identity_test_environment.cc
+++ b/services/identity/public/cpp/identity_test_environment.cc
@@ -263,8 +263,12 @@
 #endif
 
 #if !defined(OS_ANDROID) && !defined(OS_IOS)
+    // TODO(https://crbug.com/928677): Once there is no more usage of ctors
+    // that allows tests to pass backing object, |dependencies_owner_| null
+    // pointer condition is not neccessary.
     accounts_mutator = std::make_unique<AccountsMutatorImpl>(
-        token_service_, account_tracker_service_, signin_manager_);
+        token_service_, account_tracker_service_, signin_manager_,
+        dependencies_owner_ ? dependencies_owner_->pref_service() : nullptr);
 #endif
 
     std::unique_ptr<AccountsCookieMutator> accounts_cookie_mutator =
diff --git a/services/identity/public/cpp/test_identity_manager_observer.cc b/services/identity/public/cpp/test_identity_manager_observer.cc
index b7e170d..c76d493 100644
--- a/services/identity/public/cpp/test_identity_manager_observer.cc
+++ b/services/identity/public/cpp/test_identity_manager_observer.cc
@@ -75,15 +75,13 @@
   return error_from_error_state_of_refresh_token_updated_callback_;
 }
 
-// This method uses a RepeatingCallback to simplify verification of multiple
-// removed tokens.
 void TestIdentityManagerObserver::SetOnRefreshTokenRemovedCallback(
-    base::RepeatingCallback<void(const std::string&)> callback) {
+    base::OnceClosure callback) {
   on_refresh_token_removed_callback_ = std::move(callback);
 }
 
 const std::string&
-TestIdentityManagerObserver::AccountFromRefreshTokenRemovedCallback() {
+TestIdentityManagerObserver::AccountIdFromRefreshTokenRemovedCallback() {
   return account_from_refresh_token_removed_callback_;
 }
 
@@ -186,7 +184,7 @@
   batch_change_records_.rbegin()->emplace_back(account_id);
   account_from_refresh_token_removed_callback_ = account_id;
   if (on_refresh_token_removed_callback_)
-    on_refresh_token_removed_callback_.Run(account_id);
+    std::move(on_refresh_token_removed_callback_).Run();
 }
 
 void TestIdentityManagerObserver::OnErrorStateOfRefreshTokenUpdatedForAccount(
diff --git a/services/identity/public/cpp/test_identity_manager_observer.h b/services/identity/public/cpp/test_identity_manager_observer.h
index 65639c6..7fccd33 100644
--- a/services/identity/public/cpp/test_identity_manager_observer.h
+++ b/services/identity/public/cpp/test_identity_manager_observer.h
@@ -41,11 +41,8 @@
   const GoogleServiceAuthError&
   ErrorFromErrorStateOfRefreshTokenUpdatedCallback() const;
 
-  // This method uses a RepeatingCallback to simplify verification of multiple
-  // removed tokens.
-  void SetOnRefreshTokenRemovedCallback(
-      base::RepeatingCallback<void(const std::string&)> callback);
-  const std::string& AccountFromRefreshTokenRemovedCallback();
+  void SetOnRefreshTokenRemovedCallback(base::OnceClosure callback);
+  const std::string& AccountIdFromRefreshTokenRemovedCallback();
 
   void SetOnRefreshTokensLoadedCallback(base::OnceClosure callback);
 
@@ -118,8 +115,7 @@
   GoogleServiceAuthError
       error_from_error_state_of_refresh_token_updated_callback_;
 
-  base::RepeatingCallback<void(const std::string&)>
-      on_refresh_token_removed_callback_;
+  base::OnceClosure on_refresh_token_removed_callback_;
   std::string account_from_refresh_token_removed_callback_;
 
   base::OnceClosure on_refresh_tokens_loaded_callback_;
diff --git a/services/network/public/cpp/cors/cors.cc b/services/network/public/cpp/cors/cors.cc
index 29d5ec5..c84c25b3 100644
--- a/services/network/public/cpp/cors/cors.cc
+++ b/services/network/public/cpp/cors/cors.cc
@@ -359,21 +359,38 @@
   // Treat 'Intervention' as a CORS-safelisted header, since it is added by
   // Chrome when an intervention is (or may be) applied.
   static const char* const safe_names[] = {
-      "accept", "accept-language", "content-language", "intervention",
-      "content-type", "save-data",
+      "accept",
+      "accept-language",
+      "content-language",
+      "intervention",
+      "content-type",
+      "save-data",
       // The Device Memory header field is a number that indicates the client’s
       // device memory i.e. approximate amount of ram in GiB. The header value
       // must satisfy ABNF  1*DIGIT [ "." 1*DIGIT ]
       // See
       // https://w3c.github.io/device-memory/#sec-device-memory-client-hint-header
       // for more details.
-      "device-memory", "dpr", "width", "viewport-width",
+      "device-memory",
+      "dpr",
+      "width",
+      "viewport-width",
 
       // The `Sec-CH-Lang` header field is a proposed replacement for
       // `Accept-Language`, using the Client Hints infrastructure.
       //
       // https://tools.ietf.org/html/draft-west-lang-client-hint
-      "sec-ch-lang"};
+      "sec-ch-lang",
+
+      // The `Sec-CH-UA-*` header fields are proposed replacements for
+      // `User-Agent`, using the Client Hints infrastructure.
+      //
+      // https://tools.ietf.org/html/draft-west-ua-client-hints
+      "sec-ch-ua",
+      "sec-ch-ua-platform",
+      "sec-ch-ua-arch",
+      "sec-ch-ua-model",
+  };
   const std::string lower_name = base::ToLowerASCII(name);
   if (std::find(std::begin(safe_names), std::end(safe_names), lower_name) ==
       std::end(safe_names))
diff --git a/services/network/public/cpp/cors/cors_unittest.cc b/services/network/public/cpp/cors/cors_unittest.cc
index 2a24891..2fdda5d2 100644
--- a/services/network/public/cpp/cors/cors_unittest.cc
+++ b/services/network/public/cpp/cors/cors_unittest.cc
@@ -386,6 +386,16 @@
   // https://crbug.com/924969
 }
 
+TEST_F(CorsTest, SafelistedSecCHUA) {
+  EXPECT_TRUE(IsCorsSafelistedHeader("Sec-CH-UA", "\"User Agent!\""));
+  EXPECT_TRUE(IsCorsSafelistedHeader("Sec-CH-UA-Platform", "\"Platform!\""));
+  EXPECT_TRUE(IsCorsSafelistedHeader("Sec-CH-UA-Arch", "\"Architecture!\""));
+  EXPECT_TRUE(IsCorsSafelistedHeader("Sec-CH-UA-Model", "\"Model!\""));
+
+  // TODO(mkwst): Validate that `Sec-CH-UA-*` is a structured header.
+  // https://crbug.com/924969
+}
+
 TEST_F(CorsTest, SafelistedContentLanguage) {
   EXPECT_TRUE(IsCorsSafelistedHeader("content-language", "en,ja"));
   EXPECT_TRUE(IsCorsSafelistedHeader("cONTent-LANguaGe", "en,ja"));
diff --git a/services/network/websocket_factory.cc b/services/network/websocket_factory.cc
index a61bb29..a933adf 100644
--- a/services/network/websocket_factory.cc
+++ b/services/network/websocket_factory.cc
@@ -7,6 +7,7 @@
 #include "base/bind.h"
 #include "base/memory/weak_ptr.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
+#include "net/base/url_util.h"
 #include "services/network/network_context.h"
 #include "services/network/network_service.h"
 #include "services/network/public/mojom/network_service.mojom.h"
@@ -63,10 +64,7 @@
 
   bool CanReadRawCookies(const GURL& url) override {
     DCHECK(url.SchemeIsWSOrWSS());
-    GURL::Replacements replace_scheme;
-    replace_scheme.SetSchemeStr(
-        url.SchemeIs(url::kWssScheme) ? url::kHttpsScheme : url::kHttpScheme);
-    GURL url_to_check = url.ReplaceComponents(replace_scheme);
+    GURL url_to_check = net::ChangeWebSocketSchemeToHttpScheme(url);
     return factory_->context_->network_service()->HasRawHeadersAccess(
         process_id_, url_to_check);
   }
diff --git a/storage/browser/BUILD.gn b/storage/browser/BUILD.gn
index d40378c4..f00fd1e 100644
--- a/storage/browser/BUILD.gn
+++ b/storage/browser/BUILD.gn
@@ -87,6 +87,8 @@
     "fileapi/file_system_context.h",
     "fileapi/file_system_dir_url_request_job.cc",
     "fileapi/file_system_dir_url_request_job.h",
+    "fileapi/file_system_features.cc",
+    "fileapi/file_system_features.h",
     "fileapi/file_system_file_stream_reader.cc",
     "fileapi/file_system_file_stream_reader.h",
     "fileapi/file_system_file_util.cc",
@@ -125,14 +127,21 @@
     "fileapi/local_file_stream_writer.h",
     "fileapi/local_file_util.cc",
     "fileapi/local_file_util.h",
+    "fileapi/memory_file_stream_reader.cc",
+    "fileapi/memory_file_stream_reader.h",
+    "fileapi/memory_file_stream_writer.cc",
+    "fileapi/memory_file_stream_writer.h",
     "fileapi/mount_points.cc",
     "fileapi/mount_points.h",
     "fileapi/native_file_util.cc",
     "fileapi/native_file_util.h",
     "fileapi/obfuscated_file_util.cc",
     "fileapi/obfuscated_file_util.h",
+    "fileapi/obfuscated_file_util_delegate.h",
     "fileapi/obfuscated_file_util_disk_delegate.cc",
     "fileapi/obfuscated_file_util_disk_delegate.h",
+    "fileapi/obfuscated_file_util_memory_delegate.cc",
+    "fileapi/obfuscated_file_util_memory_delegate.h",
     "fileapi/open_file_system_mode.h",
     "fileapi/plugin_private_file_system_backend.cc",
     "fileapi/plugin_private_file_system_backend.h",
diff --git a/storage/browser/fileapi/file_stream_reader.h b/storage/browser/fileapi/file_stream_reader.h
index 1aeaf0e..d5bd883 100644
--- a/storage/browser/fileapi/file_stream_reader.h
+++ b/storage/browser/fileapi/file_stream_reader.h
@@ -10,6 +10,7 @@
 #include "base/compiler_specific.h"
 #include "base/component_export.h"
 #include "base/files/file.h"
+#include "base/memory/weak_ptr.h"
 #include "net/base/completion_once_callback.h"
 
 namespace base {
@@ -25,6 +26,7 @@
 namespace storage {
 class FileSystemContext;
 class FileSystemURL;
+class ObfuscatedFileUtilMemoryDelegate;
 }
 
 namespace storage {
@@ -48,6 +50,22 @@
       int64_t initial_offset,
       const base::Time& expected_modification_time);
 
+  // Creates a new FileReader for a memory file |file_path|.
+  // |initial_offset| specifies the offset in the file where the first read
+  // should start.  If the given offset is out of the file range any
+  // read operation may error out with net::ERR_REQUEST_RANGE_NOT_SATISFIABLE.
+  // |expected_modification_time| specifies the expected last modification
+  // If the value is non-null, the reader will check the underlying file's
+  // actual modification time to see if the file has been modified, and if
+  // it does any succeeding read operations should fail with
+  // ERR_UPLOAD_FILE_CHANGED error.
+  COMPONENT_EXPORT(STORAGE_BROWSER)
+  static FileStreamReader* CreateForMemoryFile(
+      base::WeakPtr<ObfuscatedFileUtilMemoryDelegate> memory_file_util,
+      const base::FilePath& file_path,
+      int64_t initial_offset,
+      const base::Time& expected_modification_time);
+
   // Creates a new reader for a filesystem URL |url| form |initial_offset|.
   // |expected_modification_time| specifies the expected last modification if
   // the value is non-null, the reader will check the underlying file's actual
diff --git a/storage/browser/fileapi/file_stream_writer.h b/storage/browser/fileapi/file_stream_writer.h
index 0d899c0..921e1837 100644
--- a/storage/browser/fileapi/file_stream_writer.h
+++ b/storage/browser/fileapi/file_stream_writer.h
@@ -8,6 +8,7 @@
 #include <stdint.h>
 
 #include "base/component_export.h"
+#include "base/memory/weak_ptr.h"
 #include "net/base/completion_once_callback.h"
 
 namespace base {
@@ -20,6 +21,10 @@
 }
 
 namespace storage {
+class ObfuscatedFileUtilMemoryDelegate;
+}
+
+namespace storage {
 
 // A generic interface for writing to a file-like object.
 class FileStreamWriter {
@@ -34,6 +39,15 @@
                                               int64_t initial_offset,
                                               OpenOrCreate open_or_create);
 
+  // Creates a writer for the existing memory file in the path |file_path|
+  // starting from |initial_offset|.
+  COMPONENT_EXPORT(STORAGE_BROWSER)
+  static FileStreamWriter* CreateForMemoryFile(
+      base::WeakPtr<ObfuscatedFileUtilMemoryDelegate> memory_file_util,
+      const base::FilePath& file_path,
+      int64_t initial_offset,
+      OpenOrCreate open_or_create);
+
   // Closes the file. If there's an in-flight operation, it is canceled (i.e.,
   // the callback function associated with the operation is not called).
   virtual ~FileStreamWriter() {}
diff --git a/storage/browser/fileapi/file_system_context.h b/storage/browser/fileapi/file_system_context.h
index 9e4c99f..ab494b9 100644
--- a/storage/browser/fileapi/file_system_context.h
+++ b/storage/browser/fileapi/file_system_context.h
@@ -308,6 +308,8 @@
                                    OpenFileSystemMode mode,
                                    StatusCallback callback);
 
+  bool is_incognito() { return is_incognito_; }
+
  private:
   // For CreateFileSystemOperation.
   friend class FileSystemOperationRunner;
diff --git a/storage/browser/fileapi/file_system_features.cc b/storage/browser/fileapi/file_system_features.cc
new file mode 100644
index 0000000..b573b76d
--- /dev/null
+++ b/storage/browser/fileapi/file_system_features.cc
@@ -0,0 +1,16 @@
+// Copyright 2019 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 "storage/browser/fileapi/file_system_features.h"
+
+namespace storage {
+
+namespace features {
+
+// Enables Filesystem API in incognito mode.
+const base::Feature kEnableFilesystemInIncognito{
+    "EnableFilesystemInIncognito", base::FEATURE_DISABLED_BY_DEFAULT};
+}  // namespace features
+
+}  // namespace storage
diff --git a/storage/browser/fileapi/file_system_features.h b/storage/browser/fileapi/file_system_features.h
new file mode 100644
index 0000000..7c5861b3
--- /dev/null
+++ b/storage/browser/fileapi/file_system_features.h
@@ -0,0 +1,22 @@
+// Copyright 2019 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 STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_FEATURES_H_
+#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_FEATURES_H_
+
+#include "base/component_export.h"
+#include "base/feature_list.h"
+
+namespace storage {
+
+namespace features {
+
+COMPONENT_EXPORT(STORAGE_BROWSER)
+extern const base::Feature kEnableFilesystemInIncognito;
+
+}  // namespace features
+
+}  // namespace storage
+
+#endif  // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_FEATURES_H_
\ No newline at end of file
diff --git a/storage/browser/fileapi/file_system_file_stream_reader.cc b/storage/browser/fileapi/file_system_file_stream_reader.cc
index 8511e21..0d0bd7b 100644
--- a/storage/browser/fileapi/file_system_file_stream_reader.cc
+++ b/storage/browser/fileapi/file_system_file_stream_reader.cc
@@ -13,6 +13,7 @@
 #include "net/base/io_buffer.h"
 #include "net/base/net_errors.h"
 #include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_features.h"
 #include "storage/browser/fileapi/file_system_operation_runner.h"
 
 using storage::FileStreamReader;
@@ -49,8 +50,8 @@
 int FileSystemFileStreamReader::Read(net::IOBuffer* buf,
                                      int buf_len,
                                      net::CompletionOnceCallback callback) {
-  if (local_file_reader_)
-    return local_file_reader_->Read(buf, buf_len, std::move(callback));
+  if (file_reader_)
+    return file_reader_->Read(buf, buf_len, std::move(callback));
 
   read_buf_ = buf;
   read_buf_len_ = buf_len;
@@ -60,8 +61,8 @@
 
 int64_t FileSystemFileStreamReader::GetLength(
     net::Int64CompletionOnceCallback callback) {
-  if (local_file_reader_)
-    return local_file_reader_->GetLength(std::move(callback));
+  if (file_reader_)
+    return file_reader_->GetLength(std::move(callback));
 
   get_length_callback_ = std::move(callback);
   return CreateSnapshot();
@@ -82,7 +83,7 @@
     const base::FilePath& platform_path,
     scoped_refptr<storage::ShareableFileReference> file_ref) {
   DCHECK(has_pending_create_snapshot_);
-  DCHECK(!local_file_reader_.get());
+  DCHECK(!file_reader_.get());
   has_pending_create_snapshot_ = false;
 
   if (file_error != base::File::FILE_OK) {
@@ -98,9 +99,16 @@
   // Keep the reference (if it's non-null) so that the file won't go away.
   snapshot_ref_ = std::move(file_ref);
 
-  local_file_reader_.reset(FileStreamReader::CreateForLocalFile(
-      file_system_context_->default_file_task_runner(), platform_path,
-      initial_offset_, expected_modification_time_));
+  if (file_system_context_->is_incognito() &&
+      base::FeatureList::IsEnabled(features::kEnableFilesystemInIncognito)) {
+    file_reader_.reset(FileStreamReader::CreateForMemoryFile(
+        file_system_context_->sandbox_delegate()->memory_file_util_delegate(),
+        platform_path, initial_offset_, expected_modification_time_));
+  } else {
+    file_reader_.reset(FileStreamReader::CreateForLocalFile(
+        file_system_context_->default_file_task_runner(), platform_path,
+        initial_offset_, expected_modification_time_));
+  }
 
   if (read_callback_) {
     DCHECK(!get_length_callback_);
@@ -112,7 +120,7 @@
     return;
   }
 
-  int rv = local_file_reader_->GetLength(base::BindOnce(
+  int rv = file_reader_->GetLength(base::BindOnce(
       &FileSystemFileStreamReader::OnGetLength, weak_factory_.GetWeakPtr()));
   if (rv != net::ERR_IO_PENDING)
     std::move(get_length_callback_).Run(rv);
diff --git a/storage/browser/fileapi/file_system_file_stream_reader.h b/storage/browser/fileapi/file_system_file_stream_reader.h
index 7bba41a..8239c76 100644
--- a/storage/browser/fileapi/file_system_file_stream_reader.h
+++ b/storage/browser/fileapi/file_system_file_stream_reader.h
@@ -74,7 +74,7 @@
   FileSystemURL url_;
   const int64_t initial_offset_;
   const base::Time expected_modification_time_;
-  std::unique_ptr<storage::FileStreamReader> local_file_reader_;
+  std::unique_ptr<storage::FileStreamReader> file_reader_;
   scoped_refptr<storage::ShareableFileReference> snapshot_ref_;
   bool has_pending_create_snapshot_;
   base::WeakPtrFactory<FileSystemFileStreamReader> weak_factory_;
diff --git a/storage/browser/fileapi/memory_file_stream_reader.cc b/storage/browser/fileapi/memory_file_stream_reader.cc
new file mode 100644
index 0000000..ada3541
--- /dev/null
+++ b/storage/browser/fileapi/memory_file_stream_reader.cc
@@ -0,0 +1,43 @@
+// Copyright 2019 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 "storage/browser/fileapi/memory_file_stream_reader.h"
+
+namespace storage {
+
+FileStreamReader* FileStreamReader::CreateForMemoryFile(
+    base::WeakPtr<ObfuscatedFileUtilMemoryDelegate> memory_file_util,
+    const base::FilePath& file_path,
+    int64_t initial_offset,
+    const base::Time& expected_modification_time) {
+  return new MemoryFileStreamReader(std::move(memory_file_util), file_path,
+                                    initial_offset, expected_modification_time);
+}
+
+MemoryFileStreamReader::~MemoryFileStreamReader() = default;
+
+int MemoryFileStreamReader::Read(net::IOBuffer* buf,
+                                 int buf_len,
+                                 net::CompletionOnceCallback callback) {
+  // TODO(https://crbug.com/93417): Implement!
+  NOTIMPLEMENTED();
+  return 0;
+}
+
+int64_t MemoryFileStreamReader::GetLength(
+    net::Int64CompletionOnceCallback callback) {
+  NOTIMPLEMENTED();
+  return 0;
+}
+
+MemoryFileStreamReader::MemoryFileStreamReader(
+    base::WeakPtr<ObfuscatedFileUtilMemoryDelegate> memory_file_util,
+    const base::FilePath& file_path,
+    int64_t initial_offset,
+    const base::Time& expected_modification_time)
+    : memory_file_util_(std::move(memory_file_util)) {
+  DCHECK(memory_file_util_);
+}
+
+}  // namespace storage
diff --git a/storage/browser/fileapi/memory_file_stream_reader.h b/storage/browser/fileapi/memory_file_stream_reader.h
new file mode 100644
index 0000000..660ea5c
--- /dev/null
+++ b/storage/browser/fileapi/memory_file_stream_reader.h
@@ -0,0 +1,47 @@
+// Copyright 2019 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 STORAGE_BROWSER_FILEAPI_MEMORY_FILE_STREAM_READER_H_
+#define STORAGE_BROWSER_FILEAPI_MEMORY_FILE_STREAM_READER_H_
+
+#include "base/component_export.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "net/base/completion_once_callback.h"
+#include "storage/browser/fileapi/file_stream_reader.h"
+#include "storage/browser/fileapi/obfuscated_file_util_memory_delegate.h"
+
+namespace storage {
+
+// A stream reader for memory files.
+class COMPONENT_EXPORT(STORAGE_BROWSER) MemoryFileStreamReader
+    : public FileStreamReader {
+ public:
+  ~MemoryFileStreamReader() override;
+
+  // FileStreamReader overrides.
+  int Read(net::IOBuffer* buf,
+           int buf_len,
+           net::CompletionOnceCallback callback) override;
+  int64_t GetLength(net::Int64CompletionOnceCallback callback) override;
+
+ private:
+  friend class FileStreamReader;
+
+  MemoryFileStreamReader(
+      base::WeakPtr<ObfuscatedFileUtilMemoryDelegate> memory_file_util,
+      const base::FilePath& file_path,
+      int64_t initial_offset,
+      const base::Time& expected_modification_time);
+
+  base::WeakPtr<ObfuscatedFileUtilMemoryDelegate> memory_file_util_;
+
+  DISALLOW_COPY_AND_ASSIGN(MemoryFileStreamReader);
+};
+
+}  // namespace storage
+
+#endif  // STORAGE_BROWSER_FILEAPI_MEMORY_FILE_STREAM_READER_H_
diff --git a/storage/browser/fileapi/memory_file_stream_writer.cc b/storage/browser/fileapi/memory_file_stream_writer.cc
new file mode 100644
index 0000000..7089e371
--- /dev/null
+++ b/storage/browser/fileapi/memory_file_stream_writer.cc
@@ -0,0 +1,47 @@
+// Copyright 2019 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 "storage/browser/fileapi/memory_file_stream_writer.h"
+
+namespace storage {
+
+FileStreamWriter* FileStreamWriter::CreateForMemoryFile(
+    base::WeakPtr<ObfuscatedFileUtilMemoryDelegate> memory_file_util,
+    const base::FilePath& file_path,
+    int64_t initial_offset,
+    OpenOrCreate open_or_create) {
+  return new MemoryFileStreamWriter(std::move(memory_file_util), file_path,
+                                    initial_offset, open_or_create);
+}
+
+MemoryFileStreamWriter::~MemoryFileStreamWriter() = default;
+
+int MemoryFileStreamWriter::Write(net::IOBuffer* buf,
+                                  int buf_len,
+                                  net::CompletionOnceCallback callback) {
+  // TODO(https://crbug.com/93417): Implement!
+  NOTIMPLEMENTED();
+  return 0;
+}
+
+int MemoryFileStreamWriter::Cancel(net::CompletionOnceCallback callback) {
+  NOTIMPLEMENTED();
+  return 0;
+}
+
+int MemoryFileStreamWriter::Flush(net::CompletionOnceCallback callback) {
+  NOTIMPLEMENTED();
+
+  return 0;
+}
+
+MemoryFileStreamWriter::MemoryFileStreamWriter(
+    base::WeakPtr<ObfuscatedFileUtilMemoryDelegate> memory_file_util,
+    const base::FilePath& file_path,
+    int64_t initial_offset,
+    OpenOrCreate open_or_create)
+    : memory_file_util_(std::move(memory_file_util)) {
+  DCHECK(memory_file_util_);
+}
+}  // namespace storage
diff --git a/storage/browser/fileapi/memory_file_stream_writer.h b/storage/browser/fileapi/memory_file_stream_writer.h
new file mode 100644
index 0000000..de6b39f
--- /dev/null
+++ b/storage/browser/fileapi/memory_file_stream_writer.h
@@ -0,0 +1,44 @@
+// Copyright 2019 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 STORAGE_BROWSER_FILEAPI_MEMORY_FILE_STREAM_WRITER_H_
+#define STORAGE_BROWSER_FILEAPI_MEMORY_FILE_STREAM_WRITER_H_
+
+#include "base/callback.h"
+#include "base/component_export.h"
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "storage/browser/fileapi/file_stream_writer.h"
+
+namespace storage {
+
+// This is a stream writer for in-memory files.
+class COMPONENT_EXPORT(STORAGE_BROWSER) MemoryFileStreamWriter
+    : public FileStreamWriter {
+ public:
+  ~MemoryFileStreamWriter() override;
+
+  // FileStreamWriter overrides.
+  int Write(net::IOBuffer* buf,
+            int buf_len,
+            net::CompletionOnceCallback callback) override;
+  int Cancel(net::CompletionOnceCallback callback) override;
+  int Flush(net::CompletionOnceCallback callback) override;
+
+ private:
+  friend class FileStreamWriter;
+  MemoryFileStreamWriter(
+      base::WeakPtr<ObfuscatedFileUtilMemoryDelegate> memory_file_util,
+      const base::FilePath& file_path,
+      int64_t initial_offset,
+      OpenOrCreate open_or_create);
+
+  base::WeakPtr<ObfuscatedFileUtilMemoryDelegate> memory_file_util_;
+
+  DISALLOW_COPY_AND_ASSIGN(MemoryFileStreamWriter);
+};
+
+}  // namespace storage
+
+#endif  // STORAGE_BROWSER_FILEAPI_MEMORY_FILE_STREAM_WRITER_H_
diff --git a/storage/browser/fileapi/obfuscated_file_util.cc b/storage/browser/fileapi/obfuscated_file_util.cc
index 300432e..a6e973e 100644
--- a/storage/browser/fileapi/obfuscated_file_util.cc
+++ b/storage/browser/fileapi/obfuscated_file_util.cc
@@ -24,7 +24,10 @@
 #include "base/time/time.h"
 #include "storage/browser/fileapi/file_observers.h"
 #include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_features.h"
 #include "storage/browser/fileapi/file_system_operation_context.h"
+#include "storage/browser/fileapi/obfuscated_file_util_disk_delegate.h"
+#include "storage/browser/fileapi/obfuscated_file_util_memory_delegate.h"
 #include "storage/browser/fileapi/sandbox_file_system_backend.h"
 #include "storage/browser/fileapi/sandbox_isolated_origin_database.h"
 #include "storage/browser/fileapi/sandbox_origin_database.h"
@@ -262,17 +265,22 @@
     : special_storage_policy_(special_storage_policy),
       file_system_directory_(file_system_directory),
       env_override_(env_override),
+      is_incognito_(is_incognito),
       db_flush_delay_seconds_(10 * 60),  // 10 mins.
       get_type_string_for_url_(std::move(get_type_string_for_url)),
       known_type_strings_(known_type_strings),
-      sandbox_delegate_(sandbox_delegate),
-      delegate_(std::make_unique<ObfuscatedFileUtilDiskDelegate>()) {
-  // TODO(https://crbug.com/93417): |delegate_| to be initialized with an
-  // instance of |ObfuscatedFileUtilMemoryDelegate| if |is_incognito| is true.
+      sandbox_delegate_(sandbox_delegate) {
   DCHECK(!get_type_string_for_url_.is_null());
   DETACH_FROM_SEQUENCE(sequence_checker_);
-  DCHECK(!is_incognito ||
+  DCHECK(!is_incognito_ ||
          (env_override && leveldb_chrome::IsMemEnv(env_override)));
+
+  if (is_incognito_ &&
+      base::FeatureList::IsEnabled(features::kEnableFilesystemInIncognito)) {
+    delegate_ = std::make_unique<ObfuscatedFileUtilMemoryDelegate>();
+  } else {
+    delegate_ = std::make_unique<ObfuscatedFileUtilDiskDelegate>();
+  }
 }
 
 ObfuscatedFileUtil::~ObfuscatedFileUtil() {
diff --git a/storage/browser/fileapi/obfuscated_file_util.h b/storage/browser/fileapi/obfuscated_file_util.h
index 1c9b3ae..e80c1d6b 100644
--- a/storage/browser/fileapi/obfuscated_file_util.h
+++ b/storage/browser/fileapi/obfuscated_file_util.h
@@ -23,9 +23,7 @@
 #include "storage/browser/blob/shareable_file_reference.h"
 #include "storage/browser/fileapi/file_system_file_util.h"
 #include "storage/browser/fileapi/file_system_url.h"
-// TODO(https://crbug.com/93417): To be replaced with
-// obfuscated_file_util_delegate.h
-#include "storage/browser/fileapi/obfuscated_file_util_disk_delegate.h"
+#include "storage/browser/fileapi/obfuscated_file_util_delegate.h"
 #include "storage/browser/fileapi/sandbox_directory_database.h"
 #include "storage/browser/fileapi/sandbox_file_system_backend_delegate.h"
 #include "storage/common/fileapi/file_system_types.h"
@@ -215,6 +213,10 @@
   // This will rewrite the databases to remove traces of deleted data from disk.
   void RewriteDatabases();
 
+  bool is_incognito() { return is_incognito_; }
+
+  ObfuscatedFileUtilDelegate* delegate() { return delegate_.get(); }
+
  private:
   using FileId = SandboxDirectoryDatabase::FileId;
   using FileInfo = SandboxDirectoryDatabase::FileInfo;
@@ -330,6 +332,7 @@
   scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy_;
   base::FilePath file_system_directory_;
   leveldb::Env* env_override_;
+  bool is_incognito_;
 
   // Used to delete database after a certain period of inactivity.
   int64_t db_flush_delay_seconds_;
@@ -342,9 +345,7 @@
   // Not owned.
   SandboxFileSystemBackendDelegate* sandbox_delegate_;
 
-  // TODO(https://crbug.com/93417): To be replaced with
-  // ObfuscatedFileUtilDelegate.
-  std::unique_ptr<ObfuscatedFileUtilDiskDelegate> delegate_;
+  std::unique_ptr<ObfuscatedFileUtilDelegate> delegate_;
 
   DISALLOW_COPY_AND_ASSIGN(ObfuscatedFileUtil);
 };
diff --git a/storage/browser/fileapi/obfuscated_file_util_delegate.h b/storage/browser/fileapi/obfuscated_file_util_delegate.h
new file mode 100644
index 0000000..6a63cdf
--- /dev/null
+++ b/storage/browser/fileapi/obfuscated_file_util_delegate.h
@@ -0,0 +1,58 @@
+// Copyright 2019 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 STORAGE_BROWSER_FILEAPI_OBFUSCATED_FILE_UTIL_DELEGATE_H_
+#define STORAGE_BROWSER_FILEAPI_OBFUSCATED_FILE_UTIL_DELEGATE_H_
+
+#include "base/component_export.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "storage/browser/fileapi/native_file_util.h"
+
+namespace storage {
+
+// This delegate performs all ObfuscatedFileUtil tasks that actually touch disk.
+
+class COMPONENT_EXPORT(STORAGE_BROWSER) ObfuscatedFileUtilDelegate {
+ public:
+  ObfuscatedFileUtilDelegate() = default;
+  virtual ~ObfuscatedFileUtilDelegate() = default;
+
+  virtual bool DirectoryExists(const base::FilePath& path) = 0;
+  virtual bool DeleteFileOrDirectory(const base::FilePath& path,
+                                     bool recursive) = 0;
+  virtual bool IsLink(const base::FilePath& file_path) = 0;
+  virtual bool PathExists(const base::FilePath& path) = 0;
+
+  virtual NativeFileUtil::CopyOrMoveMode CopyOrMoveModeForDestination(
+      const FileSystemURL& dest_url,
+      bool copy) = 0;
+  virtual base::File CreateOrOpen(const base::FilePath& path,
+                                  int file_flags) = 0;
+  virtual base::File::Error EnsureFileExists(const base::FilePath& path,
+                                             bool* created) = 0;
+  virtual base::File::Error CreateDirectory(const base::FilePath& path,
+                                            bool exclusive,
+                                            bool recursive) = 0;
+  virtual base::File::Error GetFileInfo(const base::FilePath& path,
+                                        base::File::Info* file_info) = 0;
+  virtual base::File::Error Touch(const base::FilePath& path,
+                                  const base::Time& last_access_time,
+                                  const base::Time& last_modified_time) = 0;
+  virtual base::File::Error Truncate(const base::FilePath& path,
+                                     int64_t length) = 0;
+  virtual base::File::Error CopyOrMoveFile(
+      const base::FilePath& src_path,
+      const base::FilePath& dest_path,
+      FileSystemOperation::CopyOrMoveOption option,
+      NativeFileUtil::CopyOrMoveMode mode) = 0;
+  virtual base::File::Error DeleteFile(const base::FilePath& path) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ObfuscatedFileUtilDelegate);
+};
+
+}  // namespace storage
+
+#endif  // STORAGE_BROWSER_FILEAPI_OBFUSCATED_FILE_UTIL_DELEGATE_H_
diff --git a/storage/browser/fileapi/obfuscated_file_util_disk_delegate.cc b/storage/browser/fileapi/obfuscated_file_util_disk_delegate.cc
index 7d2acb3..36d34aa 100644
--- a/storage/browser/fileapi/obfuscated_file_util_disk_delegate.cc
+++ b/storage/browser/fileapi/obfuscated_file_util_disk_delegate.cc
@@ -11,6 +11,8 @@
 
 ObfuscatedFileUtilDiskDelegate::ObfuscatedFileUtilDiskDelegate() {}
 
+ObfuscatedFileUtilDiskDelegate::~ObfuscatedFileUtilDiskDelegate() {}
+
 bool ObfuscatedFileUtilDiskDelegate::DirectoryExists(
     const base::FilePath& path) {
   return base::DirectoryExists(path);
diff --git a/storage/browser/fileapi/obfuscated_file_util_disk_delegate.h b/storage/browser/fileapi/obfuscated_file_util_disk_delegate.h
index 612ebb42..50373442 100644
--- a/storage/browser/fileapi/obfuscated_file_util_disk_delegate.h
+++ b/storage/browser/fileapi/obfuscated_file_util_disk_delegate.h
@@ -9,42 +9,46 @@
 #include "base/files/file.h"
 #include "base/files/file_path.h"
 #include "storage/browser/fileapi/native_file_util.h"
+#include "storage/browser/fileapi/obfuscated_file_util_delegate.h"
 
 namespace storage {
 
 // This delegate performs all ObfuscatedFileUtil tasks that actually touch disk.
 
-// TODO(https://crbug.com/93417): To be driven from abstract class
-// |ObfuscatedFileUtilDelegate|.
-class COMPONENT_EXPORT(STORAGE_BROWSER) ObfuscatedFileUtilDiskDelegate {
+class COMPONENT_EXPORT(STORAGE_BROWSER) ObfuscatedFileUtilDiskDelegate
+    : public ObfuscatedFileUtilDelegate {
  public:
   ObfuscatedFileUtilDiskDelegate();
-  ~ObfuscatedFileUtilDiskDelegate() = default;
+  ~ObfuscatedFileUtilDiskDelegate() override;
 
-  bool DirectoryExists(const base::FilePath& path);
-  bool DeleteFileOrDirectory(const base::FilePath& path, bool recursive);
-  bool IsLink(const base::FilePath& file_path);
-  bool PathExists(const base::FilePath& path);
+  bool DirectoryExists(const base::FilePath& path) override;
+  bool DeleteFileOrDirectory(const base::FilePath& path,
+                             bool recursive) override;
+  bool IsLink(const base::FilePath& file_path) override;
+  bool PathExists(const base::FilePath& path) override;
 
   NativeFileUtil::CopyOrMoveMode CopyOrMoveModeForDestination(
       const FileSystemURL& dest_url,
-      bool copy);
-  base::File CreateOrOpen(const base::FilePath& path, int file_flags);
-  base::File::Error EnsureFileExists(const base::FilePath& path, bool* created);
+      bool copy) override;
+  base::File CreateOrOpen(const base::FilePath& path, int file_flags) override;
+  base::File::Error EnsureFileExists(const base::FilePath& path,
+                                     bool* created) override;
   base::File::Error CreateDirectory(const base::FilePath& path,
                                     bool exclusive,
-                                    bool recursive);
+                                    bool recursive) override;
   base::File::Error GetFileInfo(const base::FilePath& path,
-                                base::File::Info* file_info);
+                                base::File::Info* file_info) override;
   base::File::Error Touch(const base::FilePath& path,
                           const base::Time& last_access_time,
-                          const base::Time& last_modified_time);
-  base::File::Error Truncate(const base::FilePath& path, int64_t length);
-  base::File::Error CopyOrMoveFile(const base::FilePath& src_path,
-                                   const base::FilePath& dest_path,
-                                   FileSystemOperation::CopyOrMoveOption option,
-                                   NativeFileUtil::CopyOrMoveMode mode);
-  base::File::Error DeleteFile(const base::FilePath& path);
+                          const base::Time& last_modified_time) override;
+  base::File::Error Truncate(const base::FilePath& path,
+                             int64_t length) override;
+  base::File::Error CopyOrMoveFile(
+      const base::FilePath& src_path,
+      const base::FilePath& dest_path,
+      FileSystemOperation::CopyOrMoveOption option,
+      NativeFileUtil::CopyOrMoveMode mode) override;
+  base::File::Error DeleteFile(const base::FilePath& path) override;
 
   DISALLOW_COPY_AND_ASSIGN(ObfuscatedFileUtilDiskDelegate);
 };
diff --git a/storage/browser/fileapi/obfuscated_file_util_memory_delegate.cc b/storage/browser/fileapi/obfuscated_file_util_memory_delegate.cc
new file mode 100644
index 0000000..d7ee8f55
--- /dev/null
+++ b/storage/browser/fileapi/obfuscated_file_util_memory_delegate.cc
@@ -0,0 +1,115 @@
+// Copyright 2019 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 "storage/browser/fileapi/obfuscated_file_util_memory_delegate.h"
+
+namespace storage {
+
+ObfuscatedFileUtilMemoryDelegate::ObfuscatedFileUtilMemoryDelegate()
+    : weak_factory_(this) {}
+ObfuscatedFileUtilMemoryDelegate::~ObfuscatedFileUtilMemoryDelegate() = default;
+
+bool ObfuscatedFileUtilMemoryDelegate::DirectoryExists(
+    const base::FilePath& path) {
+  NOTIMPLEMENTED();
+  // return base::DirectoryExists(path);
+  return false;
+}
+
+bool ObfuscatedFileUtilMemoryDelegate::DeleteFileOrDirectory(
+    const base::FilePath& path,
+    bool recursive) {
+  NOTIMPLEMENTED();
+  // return base::DeleteFile(path, recursive);
+  return false;
+}
+
+bool ObfuscatedFileUtilMemoryDelegate::IsLink(const base::FilePath& file_path) {
+  NOTIMPLEMENTED();
+  // return base::IsLink(file_path);
+  return false;
+}
+
+bool ObfuscatedFileUtilMemoryDelegate::PathExists(const base::FilePath& path) {
+  NOTIMPLEMENTED();
+  // return base::PathExists(path);
+  return false;
+}
+
+NativeFileUtil::CopyOrMoveMode
+ObfuscatedFileUtilMemoryDelegate::CopyOrMoveModeForDestination(
+    const FileSystemURL& dest_url,
+    bool copy) {
+  NOTIMPLEMENTED();
+  // return NativeFileUtil::CopyOrMoveModeForDestination(dest_url, copy);
+  return NativeFileUtil::CopyOrMoveMode::MOVE;
+}
+
+base::File ObfuscatedFileUtilMemoryDelegate::CreateOrOpen(
+    const base::FilePath& path,
+    int file_flags) {
+  NOTIMPLEMENTED();
+  // return NativeFileUtil::CreateOrOpen(path, file_flags);
+  return base::File();
+}
+
+base::File::Error ObfuscatedFileUtilMemoryDelegate::EnsureFileExists(
+    const base::FilePath& path,
+    bool* created) {
+  NOTIMPLEMENTED();
+  // return NativeFileUtil::EnsureFileExists(path, created);
+  return base::File::FILE_ERROR_FAILED;
+}
+base::File::Error ObfuscatedFileUtilMemoryDelegate::CreateDirectory(
+    const base::FilePath& path,
+    bool exclusive,
+    bool recursive) {
+  NOTIMPLEMENTED();
+  // return NativeFileUtil::CreateDirectory(path, exclusive, recursive);
+  return base::File::FILE_ERROR_FAILED;
+}
+
+base::File::Error ObfuscatedFileUtilMemoryDelegate::GetFileInfo(
+    const base::FilePath& path,
+    base::File::Info* file_info) {
+  NOTIMPLEMENTED();
+  // return NativeFileUtil::GetFileInfo(path, file_info);
+  return base::File::FILE_ERROR_FAILED;
+}
+
+base::File::Error ObfuscatedFileUtilMemoryDelegate::Touch(
+    const base::FilePath& path,
+    const base::Time& last_access_time,
+    const base::Time& last_modified_time) {
+  NOTIMPLEMENTED();
+  // return NativeFileUtil::Touch(path, last_access_time, last_modified_time);
+  return base::File::FILE_ERROR_FAILED;
+}
+
+base::File::Error ObfuscatedFileUtilMemoryDelegate::Truncate(
+    const base::FilePath& path,
+    int64_t length) {
+  NOTIMPLEMENTED();
+  // return NativeFileUtil::Truncate(path, length);
+  return base::File::FILE_ERROR_FAILED;
+}
+
+base::File::Error ObfuscatedFileUtilMemoryDelegate::CopyOrMoveFile(
+    const base::FilePath& src_path,
+    const base::FilePath& dest_path,
+    FileSystemOperation::CopyOrMoveOption option,
+    NativeFileUtil::CopyOrMoveMode mode) {
+  NOTIMPLEMENTED();
+  // return NativeFileUtil::CopyOrMoveFile(src_path, dest_path, option, mode);
+  return base::File::FILE_ERROR_FAILED;
+}
+
+base::File::Error ObfuscatedFileUtilMemoryDelegate::DeleteFile(
+    const base::FilePath& path) {
+  NOTIMPLEMENTED();
+  // return NativeFileUtil::DeleteFile(path);
+  return base::File::FILE_ERROR_FAILED;
+}
+
+}  // namespace storage
diff --git a/storage/browser/fileapi/obfuscated_file_util_memory_delegate.h b/storage/browser/fileapi/obfuscated_file_util_memory_delegate.h
new file mode 100644
index 0000000..c218924
--- /dev/null
+++ b/storage/browser/fileapi/obfuscated_file_util_memory_delegate.h
@@ -0,0 +1,66 @@
+// Copyright 2019 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 STORAGE_BROWSER_FILEAPI_OBFUSCATED_FILE_UTIL_MEMORY_DELEGATE_H_
+#define STORAGE_BROWSER_FILEAPI_OBFUSCATED_FILE_UTIL_MEMORY_DELEGATE_H_
+
+#include "base/component_export.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "storage/browser/fileapi/native_file_util.h"
+#include "storage/browser/fileapi/obfuscated_file_util_delegate.h"
+
+namespace storage {
+
+// This delegate performs all ObfuscatedFileUtil tasks that require touching
+// disk and peforms them in memory.
+
+class COMPONENT_EXPORT(STORAGE_BROWSER) ObfuscatedFileUtilMemoryDelegate
+    : public ObfuscatedFileUtilDelegate {
+ public:
+  ObfuscatedFileUtilMemoryDelegate();
+  ~ObfuscatedFileUtilMemoryDelegate() override;
+
+  bool DirectoryExists(const base::FilePath& path) override;
+  bool DeleteFileOrDirectory(const base::FilePath& path,
+                             bool recursive) override;
+  bool IsLink(const base::FilePath& file_path) override;
+  bool PathExists(const base::FilePath& path) override;
+
+  NativeFileUtil::CopyOrMoveMode CopyOrMoveModeForDestination(
+      const FileSystemURL& dest_url,
+      bool copy) override;
+  base::File CreateOrOpen(const base::FilePath& path, int file_flags) override;
+  base::File::Error EnsureFileExists(const base::FilePath& path,
+                                     bool* created) override;
+  base::File::Error CreateDirectory(const base::FilePath& path,
+                                    bool exclusive,
+                                    bool recursive) override;
+  base::File::Error GetFileInfo(const base::FilePath& path,
+                                base::File::Info* file_info) override;
+  base::File::Error Touch(const base::FilePath& path,
+                          const base::Time& last_access_time,
+                          const base::Time& last_modified_time) override;
+  base::File::Error Truncate(const base::FilePath& path,
+                             int64_t length) override;
+  base::File::Error CopyOrMoveFile(
+      const base::FilePath& src_path,
+      const base::FilePath& dest_path,
+      FileSystemOperation::CopyOrMoveOption option,
+      NativeFileUtil::CopyOrMoveMode mode) override;
+  base::File::Error DeleteFile(const base::FilePath& path) override;
+
+  base::WeakPtr<ObfuscatedFileUtilMemoryDelegate> GetWeakPtr() {
+    return weak_factory_.GetWeakPtr();
+  }
+
+ private:
+  base::WeakPtrFactory<ObfuscatedFileUtilMemoryDelegate> weak_factory_;
+  DISALLOW_COPY_AND_ASSIGN(ObfuscatedFileUtilMemoryDelegate);
+};
+
+}  // namespace storage
+
+#endif  // STORAGE_BROWSER_FILEAPI_OBFUSCATED_FILE_UTIL_MEMORY_DELEGATE_H_
diff --git a/storage/browser/fileapi/obfuscated_file_util_unittest.cc b/storage/browser/fileapi/obfuscated_file_util_unittest.cc
index 6bfce69..5c83919 100644
--- a/storage/browser/fileapi/obfuscated_file_util_unittest.cc
+++ b/storage/browser/fileapi/obfuscated_file_util_unittest.cc
@@ -43,6 +43,7 @@
 #include "storage/browser/test/test_file_system_context.h"
 #include "storage/common/database/database_identifier.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/leveldatabase/leveldb_chrome.h"
 
 using content::AsyncFileTestHelper;
 using storage::FileSystemContext;
@@ -151,11 +152,14 @@
 // could theoretically be shared.  It would basically be a FSFU interface
 // compliance test, and only the subclass-specific bits that look into the
 // implementation would need to be written per-subclass.
-class ObfuscatedFileUtilTest : public testing::Test {
+class ObfuscatedFileUtilTest : public testing::Test,
+                               public ::testing::WithParamInterface<bool> {
  public:
   ObfuscatedFileUtilTest()
-      : scoped_task_environment_(
+      : is_incognito_(GetParam()),
+        scoped_task_environment_(
             base::test::ScopedTaskEnvironment::MainThreadType::IO),
+
         origin_(GURL("http://www.example.com")),
         type_(storage::kFileSystemTypeTemporary),
         sandbox_file_system_(origin_, type_),
@@ -169,7 +173,7 @@
     storage_policy_ = new MockSpecialStoragePolicy();
 
     quota_manager_ = new storage::QuotaManager(
-        false /* is_incognito */, data_dir_.GetPath(),
+        is_incognito_, data_dir_.GetPath(),
         base::ThreadTaskRunnerHandle::Get().get(), storage_policy_.get(),
         storage::GetQuotaSettingsFunc());
     storage::QuotaSettings settings;
@@ -190,6 +194,9 @@
 
     change_observers_ =
         storage::MockFileChangeObserver::CreateList(&change_observer_);
+
+    if (is_incognito_)
+      incognito_leveldb_environment_ = leveldb_chrome::NewMemEnv("FileSystem");
   }
 
   void TearDown() override {
@@ -247,11 +254,11 @@
 
   std::unique_ptr<ObfuscatedFileUtil> CreateObfuscatedFileUtil(
       storage::SpecialStoragePolicy* storage_policy) {
-    // TODO(https://crbug.com/93417): Add support for incognito tests.
     return std::unique_ptr<ObfuscatedFileUtil>(
-        ObfuscatedFileUtil::CreateForTesting(storage_policy, data_dir_path(),
-                                             /*env_override=*/nullptr,
-                                             /*is_incognito=*/false));
+        ObfuscatedFileUtil::CreateForTesting(
+            storage_policy, data_dir_path(),
+            is_incognito_ ? incognito_leveldb_environment_.get() : nullptr,
+            is_incognito_));
   }
 
   ObfuscatedFileUtil* ofu() {
@@ -680,6 +687,10 @@
   }
 
   void MaybeDropDatabasesAliveCaseTestBody() {
+    // TODO(https://crbug.com/43417): Enable test after finishing incognito
+    // implementation.
+    if (is_incognito_)
+      return;
     std::unique_ptr<ObfuscatedFileUtil> file_util =
         CreateObfuscatedFileUtil(nullptr);
     file_util->InitOriginDatabase(GURL(), true /*create*/);
@@ -710,6 +721,10 @@
   }
 
   void DestroyDirectoryDatabase_IsolatedTestBody() {
+    // TODO(https://crbug.com/43417): Enable test after finishing incognito
+    // implementation.
+    if (is_incognito_)
+      return;
     storage_policy_->AddIsolated(origin_);
     std::unique_ptr<ObfuscatedFileUtil> file_util =
         CreateObfuscatedFileUtil(storage_policy_.get());
@@ -729,6 +744,10 @@
   }
 
   void GetDirectoryDatabase_IsolatedTestBody() {
+    // TODO(https://crbug.com/43417): Enable test after finishing incognito
+    // implementation.
+    if (is_incognito_)
+      return;
     storage_policy_->AddIsolated(origin_);
     std::unique_ptr<ObfuscatedFileUtil> file_util =
         CreateObfuscatedFileUtil(storage_policy_.get());
@@ -763,6 +782,8 @@
   const base::FilePath& data_dir_path() const { return data_dir_.GetPath(); }
 
  protected:
+  bool is_incognito_;
+  std::unique_ptr<leveldb::Env> incognito_leveldb_environment_;
   base::test::ScopedTaskEnvironment scoped_task_environment_;
   base::ScopedTempDir data_dir_;
   scoped_refptr<MockSpecialStoragePolicy> storage_policy_;
@@ -781,7 +802,9 @@
   DISALLOW_COPY_AND_ASSIGN(ObfuscatedFileUtilTest);
 };
 
-TEST_F(ObfuscatedFileUtilTest, TestCreateAndDeleteFile) {
+INSTANTIATE_TEST_CASE_P(, ObfuscatedFileUtilTest, ::testing::Bool());
+
+TEST_P(ObfuscatedFileUtilTest, TestCreateAndDeleteFile) {
   FileSystemURL url = CreateURLFromUTF8("fake/file");
   std::unique_ptr<FileSystemOperationContext> context(NewContext(nullptr));
   int file_flags = base::File::FLAG_CREATE | base::File::FLAG_WRITE;
@@ -866,7 +889,7 @@
   EXPECT_TRUE(change_observer()->HasNoChange());
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestTruncate) {
+TEST_P(ObfuscatedFileUtilTest, TestTruncate) {
   bool created = false;
   FileSystemURL url = CreateURLFromUTF8("file");
   std::unique_ptr<FileSystemOperationContext> context(NewContext(nullptr));
@@ -903,7 +926,7 @@
   EXPECT_TRUE(change_observer()->HasNoChange());
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestQuotaOnTruncation) {
+TEST_P(ObfuscatedFileUtilTest, TestQuotaOnTruncation) {
   bool created = false;
   FileSystemURL url = CreateURLFromUTF8("file");
 
@@ -958,7 +981,7 @@
   ASSERT_EQ(0, ComputeTotalFileSize());
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestEnsureFileExists) {
+TEST_P(ObfuscatedFileUtilTest, TestEnsureFileExists) {
   FileSystemURL url = CreateURLFromUTF8("fake/file");
   bool created = false;
   std::unique_ptr<FileSystemOperationContext> context(NewContext(nullptr));
@@ -1013,7 +1036,7 @@
   EXPECT_TRUE(change_observer()->HasNoChange());
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestDirectoryOps) {
+TEST_P(ObfuscatedFileUtilTest, TestDirectoryOps) {
   std::unique_ptr<FileSystemOperationContext> context(NewContext(nullptr));
 
   bool exclusive = false;
@@ -1155,7 +1178,7 @@
   EXPECT_TRUE(change_observer()->HasNoChange());
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestReadDirectory) {
+TEST_P(ObfuscatedFileUtilTest, TestReadDirectory) {
   std::unique_ptr<FileSystemOperationContext> context(NewContext(nullptr));
   bool exclusive = true;
   bool recursive = true;
@@ -1165,15 +1188,15 @@
   TestReadDirectoryHelper(url);
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestReadRootWithSlash) {
+TEST_P(ObfuscatedFileUtilTest, TestReadRootWithSlash) {
   TestReadDirectoryHelper(CreateURLFromUTF8(std::string()));
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestReadRootWithEmptyString) {
+TEST_P(ObfuscatedFileUtilTest, TestReadRootWithEmptyString) {
   TestReadDirectoryHelper(CreateURLFromUTF8("/"));
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestReadDirectoryOnFile) {
+TEST_P(ObfuscatedFileUtilTest, TestReadDirectoryOnFile) {
   FileSystemURL url = CreateURLFromUTF8("file");
   std::unique_ptr<FileSystemOperationContext> context(NewContext(nullptr));
 
@@ -1190,7 +1213,7 @@
   EXPECT_TRUE(ofu()->IsDirectoryEmpty(context.get(), url));
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestTouch) {
+TEST_P(ObfuscatedFileUtilTest, TestTouch) {
   FileSystemURL url = CreateURLFromUTF8("file");
   std::unique_ptr<FileSystemOperationContext> context(NewContext(nullptr));
 
@@ -1220,7 +1243,7 @@
   TestTouchHelper(url, false);
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestPathQuotas) {
+TEST_P(ObfuscatedFileUtilTest, TestPathQuotas) {
   FileSystemURL url = CreateURLFromUTF8("fake/file");
   std::unique_ptr<FileSystemOperationContext> context(NewContext(nullptr));
 
@@ -1255,7 +1278,7 @@
   EXPECT_EQ(1024 - path_cost, context->allowed_bytes_growth());
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestCopyOrMoveFileNotFound) {
+TEST_P(ObfuscatedFileUtilTest, TestCopyOrMoveFileNotFound) {
   FileSystemURL source_url = CreateURLFromUTF8("path0.txt");
   FileSystemURL dest_url = CreateURLFromUTF8("path1.txt");
   std::unique_ptr<FileSystemOperationContext> context(NewContext(nullptr));
@@ -1297,7 +1320,7 @@
   EXPECT_TRUE(change_observer()->HasNoChange());
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestCopyOrMoveFileSuccess) {
+TEST_P(ObfuscatedFileUtilTest, TestCopyOrMoveFileSuccess) {
   const int64_t kSourceLength = 5;
   const int64_t kDestLength = 50;
 
@@ -1386,7 +1409,7 @@
   }
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestCopyPathQuotas) {
+TEST_P(ObfuscatedFileUtilTest, TestCopyPathQuotas) {
   FileSystemURL src_url = CreateURLFromUTF8("src path");
   FileSystemURL dest_url = CreateURLFromUTF8("destination path");
   std::unique_ptr<FileSystemOperationContext> context(NewContext(nullptr));
@@ -1416,7 +1439,7 @@
                                   FileSystemOperation::OPTION_NONE, is_copy));
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestMovePathQuotasWithRename) {
+TEST_P(ObfuscatedFileUtilTest, TestMovePathQuotasWithRename) {
   FileSystemURL src_url = CreateURLFromUTF8("src path");
   FileSystemURL dest_url = CreateURLFromUTF8("destination path");
   std::unique_ptr<FileSystemOperationContext> context(NewContext(nullptr));
@@ -1453,7 +1476,7 @@
                                   FileSystemOperation::OPTION_NONE, is_copy));
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestMovePathQuotasWithoutRename) {
+TEST_P(ObfuscatedFileUtilTest, TestMovePathQuotasWithoutRename) {
   FileSystemURL src_url = CreateURLFromUTF8("src path");
   std::unique_ptr<FileSystemOperationContext> context(NewContext(nullptr));
   bool created = false;
@@ -1496,12 +1519,12 @@
       context->allowed_bytes_growth());
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestCopyInForeignFile) {
+TEST_P(ObfuscatedFileUtilTest, TestCopyInForeignFile) {
   TestCopyInForeignFileHelper(false /* overwrite */);
   TestCopyInForeignFileHelper(true /* overwrite */);
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestEnumerator) {
+TEST_P(ObfuscatedFileUtilTest, TestEnumerator) {
   std::unique_ptr<FileSystemOperationContext> context(NewContext(nullptr));
   FileSystemURL src_url = CreateURLFromUTF8("source dir");
   bool exclusive = true;
@@ -1531,7 +1554,7 @@
   EXPECT_FALSE(DirectoryExists(dest_url));
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestOriginEnumerator) {
+TEST_P(ObfuscatedFileUtilTest, TestOriginEnumerator) {
   std::unique_ptr<ObfuscatedFileUtil::AbstractOriginEnumerator> enumerator(
       ofu()->CreateOriginEnumerator());
   // The test helper starts out with a single filesystem.
@@ -1617,7 +1640,7 @@
   EXPECT_TRUE(diff.empty());
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestRevokeUsageCache) {
+TEST_P(ObfuscatedFileUtilTest, TestRevokeUsageCache) {
   std::unique_ptr<FileSystemOperationContext> context(NewContext(nullptr));
 
   int64_t expected_quota = 0;
@@ -1663,7 +1686,7 @@
   EXPECT_EQ(expected_quota, usage());
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestInconsistency) {
+TEST_P(ObfuscatedFileUtilTest, TestInconsistency) {
   const FileSystemURL kPath1 = CreateURLFromUTF8("hoge");
   const FileSystemURL kPath2 = CreateURLFromUTF8("fuga");
 
@@ -1738,7 +1761,7 @@
   EXPECT_EQ(0, file_info.size);
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestIncompleteDirectoryReading) {
+TEST_P(ObfuscatedFileUtilTest, TestIncompleteDirectoryReading) {
   const FileSystemURL kPath[] = {
     CreateURLFromUTF8("foo"),
     CreateURLFromUTF8("bar"),
@@ -1773,7 +1796,7 @@
   EXPECT_EQ(base::size(kPath) - 1, entries.size());
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestDirectoryTimestampForCreation) {
+TEST_P(ObfuscatedFileUtilTest, TestDirectoryTimestampForCreation) {
   std::unique_ptr<FileSystemOperationContext> context(NewContext(nullptr));
   const FileSystemURL dir_url = CreateURLFromUTF8("foo_dir");
 
@@ -1898,7 +1921,7 @@
   EXPECT_NE(base::Time(), GetModifiedTime(dir_url));
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestDirectoryTimestampForDeletion) {
+TEST_P(ObfuscatedFileUtilTest, TestDirectoryTimestampForDeletion) {
   std::unique_ptr<FileSystemOperationContext> context(NewContext(nullptr));
   const FileSystemURL dir_url = CreateURLFromUTF8("foo_dir");
 
@@ -1957,7 +1980,7 @@
   EXPECT_NE(base::Time(), GetModifiedTime(dir_url));
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestDirectoryTimestampForCopyAndMove) {
+TEST_P(ObfuscatedFileUtilTest, TestDirectoryTimestampForCopyAndMove) {
   TestDirectoryTimestampHelper(
       CreateURLFromUTF8("copy overwrite"), true, true);
   TestDirectoryTimestampHelper(
@@ -1968,7 +1991,7 @@
       CreateURLFromUTF8("move non-overwrite"), false, false);
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestFileEnumeratorTimestamp) {
+TEST_P(ObfuscatedFileUtilTest, TestFileEnumeratorTimestamp) {
   FileSystemURL dir = CreateURLFromUTF8("foo");
   FileSystemURL url1 = FileSystemURLAppendUTF8(dir, "bar");
   FileSystemURL url2 = FileSystemURLAppendUTF8(dir, "baz");
@@ -2030,7 +2053,7 @@
 #else
 #define MAYBE_TestQuotaOnCopyFile TestQuotaOnCopyFile
 #endif
-TEST_F(ObfuscatedFileUtilTest, MAYBE_TestQuotaOnCopyFile) {
+TEST_P(ObfuscatedFileUtilTest, MAYBE_TestQuotaOnCopyFile) {
   FileSystemURL from_file(CreateURLFromUTF8("fromfile"));
   FileSystemURL obstacle_file(CreateURLFromUTF8("obstaclefile"));
   FileSystemURL to_file1(CreateURLFromUTF8("tofile1"));
@@ -2128,7 +2151,7 @@
   }
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestQuotaOnMoveFile) {
+TEST_P(ObfuscatedFileUtilTest, TestQuotaOnMoveFile) {
   FileSystemURL from_file(CreateURLFromUTF8("fromfile"));
   FileSystemURL obstacle_file(CreateURLFromUTF8("obstaclefile"));
   FileSystemURL to_file(CreateURLFromUTF8("tofile"));
@@ -2234,7 +2257,7 @@
   context.reset();
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestQuotaOnRemove) {
+TEST_P(ObfuscatedFileUtilTest, TestQuotaOnRemove) {
   FileSystemURL dir(CreateURLFromUTF8("dir"));
   FileSystemURL file(CreateURLFromUTF8("file"));
   FileSystemURL dfile1(CreateURLFromUTF8("dir/dfile1"));
@@ -2298,7 +2321,7 @@
   ASSERT_EQ(0, ComputeTotalFileSize());
 }
 
-TEST_F(ObfuscatedFileUtilTest, TestQuotaOnOpen) {
+TEST_P(ObfuscatedFileUtilTest, TestQuotaOnOpen) {
   FileSystemURL url(CreateURLFromUTF8("file"));
 
   bool created;
@@ -2347,23 +2370,23 @@
   file.Close();
 }
 
-TEST_F(ObfuscatedFileUtilTest, MaybeDropDatabasesAliveCase) {
+TEST_P(ObfuscatedFileUtilTest, MaybeDropDatabasesAliveCase) {
   MaybeDropDatabasesAliveCaseTestBody();
 }
 
-TEST_F(ObfuscatedFileUtilTest, MaybeDropDatabasesAlreadyDeletedCase) {
+TEST_P(ObfuscatedFileUtilTest, MaybeDropDatabasesAlreadyDeletedCase) {
   MaybeDropDatabasesAlreadyDeletedCaseTestBody();
 }
 
-TEST_F(ObfuscatedFileUtilTest, DestroyDirectoryDatabase_Isolated) {
+TEST_P(ObfuscatedFileUtilTest, DestroyDirectoryDatabase_Isolated) {
   DestroyDirectoryDatabase_IsolatedTestBody();
 }
 
-TEST_F(ObfuscatedFileUtilTest, GetDirectoryDatabase_Isolated) {
+TEST_P(ObfuscatedFileUtilTest, GetDirectoryDatabase_Isolated) {
   GetDirectoryDatabase_IsolatedTestBody();
 }
 
-TEST_F(ObfuscatedFileUtilTest, OpenPathInNonDirectory) {
+TEST_P(ObfuscatedFileUtilTest, OpenPathInNonDirectory) {
   FileSystemURL url(CreateURLFromUTF8("file"));
   FileSystemURL path_in_file(CreateURLFromUTF8("file/file"));
   bool created;
@@ -2385,7 +2408,7 @@
                                    false /* recursive */));
 }
 
-TEST_F(ObfuscatedFileUtilTest, CreateDirectory_NotADirectoryInRecursive) {
+TEST_P(ObfuscatedFileUtilTest, CreateDirectory_NotADirectoryInRecursive) {
   FileSystemURL file(CreateURLFromUTF8("file"));
   FileSystemURL path_in_file(CreateURLFromUTF8("file/child"));
   FileSystemURL path_in_file_in_file(
@@ -2408,7 +2431,7 @@
                                    true /* recursive */));
 }
 
-TEST_F(ObfuscatedFileUtilTest, DeleteDirectoryForOriginAndType) {
+TEST_P(ObfuscatedFileUtilTest, DeleteDirectoryForOriginAndType) {
   const GURL origin1("http://www.example.com:12");
   const GURL origin2("http://www.example.com:1234");
   const GURL origin3("http://nope.example.com");
@@ -2486,7 +2509,7 @@
       origin3, GetTypeString(kFileSystemTypePersistent)));
 }
 
-TEST_F(ObfuscatedFileUtilTest, DeleteDirectoryForOriginAndType_DeleteAll) {
+TEST_P(ObfuscatedFileUtilTest, DeleteDirectoryForOriginAndType_DeleteAll) {
   const GURL origin1("http://www.example.com:12");
   const GURL origin2("http://www.example.com:1234");
 
diff --git a/storage/browser/fileapi/sandbox_file_stream_writer.cc b/storage/browser/fileapi/sandbox_file_stream_writer.cc
index 7418c64a..857d307 100644
--- a/storage/browser/fileapi/sandbox_file_stream_writer.cc
+++ b/storage/browser/fileapi/sandbox_file_stream_writer.cc
@@ -18,6 +18,7 @@
 #include "storage/browser/fileapi/file_observers.h"
 #include "storage/browser/fileapi/file_stream_reader.h"
 #include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_features.h"
 #include "storage/browser/fileapi/file_system_operation_runner.h"
 #include "storage/browser/quota/quota_manager_proxy.h"
 #include "storage/common/fileapi/file_system_util.h"
@@ -70,7 +71,7 @@
   DCHECK(!write_callback_);
   has_pending_operation_ = true;
   write_callback_ = std::move(callback);
-  if (local_file_writer_)
+  if (file_writer_)
     return WriteInternal(buf, buf_len);
 
   net::CompletionOnceCallback write_task = base::BindOnce(
@@ -104,11 +105,11 @@
   if (buf_len > allowed_bytes_to_write_ - total_bytes_written_)
     buf_len = allowed_bytes_to_write_ - total_bytes_written_;
 
-  DCHECK(local_file_writer_.get());
-  const int result = local_file_writer_->Write(
-      buf, buf_len,
-      base::BindOnce(&SandboxFileStreamWriter::DidWrite,
-                     weak_factory_.GetWeakPtr()));
+  DCHECK(file_writer_.get());
+  const int result =
+      file_writer_->Write(buf, buf_len,
+                          base::BindOnce(&SandboxFileStreamWriter::DidWrite,
+                                         weak_factory_.GetWeakPtr()));
   if (result != net::ERR_IO_PENDING)
     has_pending_operation_ = false;
   return result;
@@ -140,13 +141,18 @@
     NOTREACHED();
     initial_offset_ = file_size_;
   }
-  DCHECK(!local_file_writer_.get());
-  local_file_writer_.reset(FileStreamWriter::CreateForLocalFile(
-      file_system_context_->default_file_task_runner(),
-      platform_path,
-      initial_offset_,
-      FileStreamWriter::OPEN_EXISTING_FILE));
+  DCHECK(!file_writer_.get());
 
+  if (file_system_context_->is_incognito() &&
+      base::FeatureList::IsEnabled(features::kEnableFilesystemInIncognito)) {
+    file_writer_.reset(FileStreamWriter::CreateForMemoryFile(
+        file_system_context_->sandbox_delegate()->memory_file_util_delegate(),
+        platform_path, initial_offset_, FileStreamWriter::OPEN_EXISTING_FILE));
+  } else {
+    file_writer_.reset(FileStreamWriter::CreateForLocalFile(
+        file_system_context_->default_file_task_runner(), platform_path,
+        initial_offset_, FileStreamWriter::OPEN_EXISTING_FILE));
+  }
   storage::QuotaManagerProxy* quota_manager_proxy =
       file_system_context_->quota_manager_proxy();
   if (!quota_manager_proxy) {
@@ -247,10 +253,10 @@
   DCHECK(cancel_callback_.is_null());
 
   // Write() is not called yet, so there's nothing to flush.
-  if (!local_file_writer_)
+  if (!file_writer_)
     return net::OK;
 
-  return local_file_writer_->Flush(std::move(callback));
+  return file_writer_->Flush(std::move(callback));
 }
 
 }  // namespace storage
diff --git a/storage/browser/fileapi/sandbox_file_stream_writer.h b/storage/browser/fileapi/sandbox_file_stream_writer.h
index 308f370..e8cb6f7 100644
--- a/storage/browser/fileapi/sandbox_file_stream_writer.h
+++ b/storage/browser/fileapi/sandbox_file_stream_writer.h
@@ -46,7 +46,7 @@
   void set_default_quota(int64_t quota) { default_quota_ = quota; }
 
  private:
-  // Performs quota calculation and calls local_file_writer_->Write().
+  // Performs quota calculation and calls file_writer_->Write().
   int WriteInternal(net::IOBuffer* buf, int buf_len);
 
   // Callbacks that are chained for the first write.  This eventually calls
@@ -72,7 +72,7 @@
   scoped_refptr<FileSystemContext> file_system_context_;
   FileSystemURL url_;
   int64_t initial_offset_;
-  std::unique_ptr<FileStreamWriter> local_file_writer_;
+  std::unique_ptr<FileStreamWriter> file_writer_;
   net::CompletionOnceCallback write_callback_;
   net::CompletionOnceCallback cancel_callback_;
 
diff --git a/storage/browser/fileapi/sandbox_file_system_backend_delegate.cc b/storage/browser/fileapi/sandbox_file_system_backend_delegate.cc
index 89afce7..d50a6e83 100644
--- a/storage/browser/fileapi/sandbox_file_system_backend_delegate.cc
+++ b/storage/browser/fileapi/sandbox_file_system_backend_delegate.cc
@@ -26,6 +26,7 @@
 #include "storage/browser/fileapi/file_system_url.h"
 #include "storage/browser/fileapi/file_system_usage_cache.h"
 #include "storage/browser/fileapi/obfuscated_file_util.h"
+#include "storage/browser/fileapi/obfuscated_file_util_memory_delegate.h"
 #include "storage/browser/fileapi/quota/quota_backend_impl.h"
 #include "storage/browser/fileapi/quota/quota_reservation.h"
 #include "storage/browser/fileapi/quota/quota_reservation_manager.h"
@@ -712,6 +713,14 @@
   return static_cast<ObfuscatedFileUtil*>(sync_file_util());
 }
 
+base::WeakPtr<ObfuscatedFileUtilMemoryDelegate>
+SandboxFileSystemBackendDelegate::memory_file_util_delegate() {
+  DCHECK(obfuscated_file_util()->is_incognito());
+  return static_cast<ObfuscatedFileUtilMemoryDelegate*>(
+             obfuscated_file_util()->delegate())
+      ->GetWeakPtr();
+}
+
 // Declared in obfuscated_file_util.h.
 // static
 ObfuscatedFileUtil* ObfuscatedFileUtil::CreateForTesting(
diff --git a/storage/browser/fileapi/sandbox_file_system_backend_delegate.h b/storage/browser/fileapi/sandbox_file_system_backend_delegate.h
index 96ae2a5..93c5b01f 100644
--- a/storage/browser/fileapi/sandbox_file_system_backend_delegate.h
+++ b/storage/browser/fileapi/sandbox_file_system_backend_delegate.h
@@ -56,6 +56,7 @@
 class FileSystemURL;
 class FileSystemUsageCache;
 class ObfuscatedFileUtil;
+class ObfuscatedFileUtilMemoryDelegate;
 class QuotaReservationManager;
 class SandboxQuotaObserver;
 
@@ -206,6 +207,8 @@
 
   FileSystemFileUtil* sync_file_util();
 
+  base::WeakPtr<ObfuscatedFileUtilMemoryDelegate> memory_file_util_delegate();
+
  private:
   friend class QuotaBackendImpl;
   friend class SandboxQuotaObserver;
diff --git a/storage/browser/quota/quota_manager.cc b/storage/browser/quota/quota_manager.cc
index 62830e0d..fe150e8 100644
--- a/storage/browser/quota/quota_manager.cc
+++ b/storage/browser/quota/quota_manager.cc
@@ -559,10 +559,14 @@
     remaining_clients_ = manager()->clients_.size();
     for (auto* client : manager()->clients_) {
       if (quota_client_mask_ & client->id()) {
+        static int tracing_id = 0;
+        TRACE_EVENT_ASYNC_BEGIN2(
+            "browsing_data", "QuotaManager::OriginDataDeleter", ++tracing_id,
+            "client_id", client->id(), "origin", origin_.Serialize());
         client->DeleteOriginData(
             origin_, type_,
             base::BindOnce(&OriginDataDeleter::DidDeleteOriginData,
-                           weak_factory_.GetWeakPtr()));
+                           weak_factory_.GetWeakPtr(), tracing_id));
       } else {
         ++skipped_clients_;
         if (--remaining_clients_ == 0)
@@ -596,8 +600,11 @@
   }
 
  private:
-  void DidDeleteOriginData(blink::mojom::QuotaStatusCode status) {
+  void DidDeleteOriginData(int tracing_id,
+                           blink::mojom::QuotaStatusCode status) {
     DCHECK_GT(remaining_clients_, 0);
+    TRACE_EVENT_ASYNC_END0("browsing_data", "QuotaManager::OriginDataDeleter",
+                           tracing_id);
 
     if (status != blink::mojom::QuotaStatusCode::kOk)
       ++error_count_;
diff --git a/third_party/blink/common/client_hints/client_hints.cc b/third_party/blink/common/client_hints/client_hints.cc
index 5777f6cc..d6a2568 100644
--- a/third_party/blink/common/client_hints/client_hints.cc
+++ b/third_party/blink/common/client_hints/client_hints.cc
@@ -10,12 +10,24 @@
 namespace blink {
 
 const char* const kClientHintsNameMapping[] = {
-    "device-memory", "dpr",      "width", "viewport-width",
-    "rtt",           "downlink", "ect",   "lang"};
+    "device-memory", "dpr",  "width", "viewport-width", "rtt",      "downlink",
+    "ect",           "lang", "ua",    "arch",           "platform", "model",
+};
 
 const char* const kClientHintsHeaderMapping[] = {
-    "device-memory", "dpr",      "width", "viewport-width",
-    "rtt",           "downlink", "ect",   "sec-ch-lang"};
+    "device-memory",
+    "dpr",
+    "width",
+    "viewport-width",
+    "rtt",
+    "downlink",
+    "ect",
+    "sec-ch-lang",
+    "sec-ch-ua",
+    "sec-ch-ua-arch",
+    "sec-ch-ua-platform",
+    "sec-ch-ua-model",
+};
 
 const size_t kClientHintsMappingsCount = base::size(kClientHintsNameMapping);
 
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 3b668afe..c54fa31 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -93,6 +93,11 @@
 // Enable Portals. https://crbug.com/865123.
 const base::Feature kPortals{"Portals", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enable limiting previews loading hints to specific resource types.
+const base::Feature kPreviewsResourceLoadingHintsSpecificResourceTypes{
+    "PreviewsResourceLoadingHintsSpecificResourceTypes",
+    base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enable Implicit Root Scroller. https://crbug.com/903260.
 const base::Feature kImplicitRootScroller{"ImplicitRootScroller",
                                           base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/third_party/blink/public/BUILD.gn b/third_party/blink/public/BUILD.gn
index f9299d7..e068fe33 100644
--- a/third_party/blink/public/BUILD.gn
+++ b/third_party/blink/public/BUILD.gn
@@ -72,7 +72,6 @@
   java_cpp_enum("blink_headers_java_enums_srcjar") {
     sources = [
       "./common/manifest/web_display_mode.h",
-      "./platform/modules/remoteplayback/web_remote_playback_availability.h",
       "./platform/web_focus_type.h",
       "./platform/web_input_event.h",
       "./platform/web_text_input_mode.h",
@@ -163,7 +162,6 @@
     "platform/modules/push_messaging/web_push_provider.h",
     "platform/modules/push_messaging/web_push_subscription.h",
     "platform/modules/push_messaging/web_push_subscription_options.h",
-    "platform/modules/remoteplayback/web_remote_playback_availability.h",
     "platform/modules/remoteplayback/web_remote_playback_client.h",
     "platform/modules/remoteplayback/web_remote_playback_state.h",
     "platform/modules/service_worker/web_service_worker_clients_info.h",
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index 5acf74f..11c8319 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -34,6 +34,8 @@
 BLINK_COMMON_EXPORT extern const base::Feature kOnionSoupDOMStorage;
 BLINK_COMMON_EXPORT extern const base::Feature kPlzDedicatedWorker;
 BLINK_COMMON_EXPORT extern const base::Feature kPortals;
+BLINK_COMMON_EXPORT extern const base::Feature
+    kPreviewsResourceLoadingHintsSpecificResourceTypes;
 BLINK_COMMON_EXPORT extern const base::Feature kRTCGetDisplayMedia;
 BLINK_COMMON_EXPORT extern const base::Feature kRTCUnifiedPlanByDefault;
 BLINK_COMMON_EXPORT extern const base::Feature kRTCOfferExtmapAllowMixed;
diff --git a/third_party/blink/public/mojom/renderer_preferences.mojom b/third_party/blink/public/mojom/renderer_preferences.mojom
index 8fcd4502..e15295c 100644
--- a/third_party/blink/public/mojom/renderer_preferences.mojom
+++ b/third_party/blink/public/mojom/renderer_preferences.mojom
@@ -40,8 +40,11 @@
   // positions for glyphs.  Currently only used by Linux.
   bool use_subpixel_positioning = false;
 
-  // The color of the focus ring. Currently only used on Linux.
   uint32 focus_ring_color = 0xFFE59700;
+  [EnableIf=is_android]
+  float minimum_stroke_width_for_focus_ring = 1.0;
+  [EnableIf=is_android]
+  bool is_focus_ring_outset = false;
 
   // The colors used in selection text. Currently only used on Linux and Ash.
   uint32 active_selection_bg_color = 0xFF1E90FF;
diff --git a/third_party/blink/public/mojom/service_worker/embedded_worker.mojom b/third_party/blink/public/mojom/service_worker/embedded_worker.mojom
index 2b941c1..5c12d7a 100644
--- a/third_party/blink/public/mojom/service_worker/embedded_worker.mojom
+++ b/third_party/blink/public/mojom/service_worker/embedded_worker.mojom
@@ -27,10 +27,6 @@
 // Parameters to launch a service worker. This is passed from the browser to the
 // renderer at mojom::EmbeddedWorkerInstanceClient::StartWorker().
 struct EmbeddedWorkerStartParams {
-  // DEPRECATED: This is only used in unit tests.
-  // TODO(https://crbug.com/927651): Remove this.
-  int32 embedded_worker_id;
-
   // The id of the service worker being started. This remains fixed even if the
   // worker is stopped and restarted, or even if the browser restarts.
   int64 service_worker_version_id;
diff --git a/third_party/blink/public/platform/modules/remoteplayback/web_remote_playback_availability.h b/third_party/blink/public/platform/modules/remoteplayback/web_remote_playback_availability.h
deleted file mode 100644
index f0697b0..0000000
--- a/third_party/blink/public/platform/modules/remoteplayback/web_remote_playback_availability.h
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2016 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 THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_REMOTEPLAYBACK_WEB_REMOTE_PLAYBACK_AVAILABILITY_H_
-#define THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_REMOTEPLAYBACK_WEB_REMOTE_PLAYBACK_AVAILABILITY_H_
-
-namespace blink {
-
-// GENERATED_JAVA_ENUM_PACKAGE: (
-//     org.chromium.blink_public.platform.modules.remoteplayback)
-// Various states for the remote playback availability.
-enum class WebRemotePlaybackAvailability {
-  // The availability is unknown.
-  kUnknown,
-
-  // The media source is compatible with some supported device types but
-  // no devices were found.
-  kDeviceNotAvailable,
-
-  // There're available devices but the current media source is not compatible
-  // with any of those.
-  kSourceNotCompatible,
-
-  // There're available remote playback devices and the media source is
-  // compatible with at least one of them.
-  kDeviceAvailable,
-
-  kLast = kDeviceAvailable
-};
-
-}  // namespace blink
-
-#endif  // WebRemotePlaybackState_h
diff --git a/third_party/blink/public/platform/modules/remoteplayback/web_remote_playback_client.h b/third_party/blink/public/platform/modules/remoteplayback/web_remote_playback_client.h
index 588fccf0..10ec2d3a 100644
--- a/third_party/blink/public/platform/modules/remoteplayback/web_remote_playback_client.h
+++ b/third_party/blink/public/platform/modules/remoteplayback/web_remote_playback_client.h
@@ -7,7 +7,6 @@
 
 namespace blink {
 
-enum class WebRemotePlaybackAvailability;
 enum class WebRemotePlaybackState;
 class WebURL;
 class WebString;
@@ -18,15 +17,6 @@
  public:
   virtual ~WebRemotePlaybackClient() = default;
 
-  // Notifies the client that the media element state has changed.
-  virtual void StateChanged(WebRemotePlaybackState) = 0;
-
-  // Notifies the client of the remote playback device availability change.
-  virtual void AvailabilityChanged(WebRemotePlaybackAvailability) = 0;
-
-  // Notifies the client that the user cancelled the prompt shown via the API.
-  virtual void PromptCancelled() = 0;
-
   // Returns if the remote playback available for this media element.
   virtual bool RemotePlaybackAvailable() const = 0;
 
diff --git a/third_party/blink/public/platform/web_client_hints_types.mojom b/third_party/blink/public/platform/web_client_hints_types.mojom
index 3a41a1c..4f9a2b13 100644
--- a/third_party/blink/public/platform/web_client_hints_types.mojom
+++ b/third_party/blink/public/platform/web_client_hints_types.mojom
@@ -24,6 +24,10 @@
   kDownlink = 5,
   kEct = 6,
   kLang = 7,
+  kUA = 8,
+  kUAArch = 9,
+  kUAPlatform = 10,
+  kUAModel = 11,
 
   // Warning: Before adding a new client hint, read the warning at the top.
 };
diff --git a/third_party/blink/public/platform/web_feature.mojom b/third_party/blink/public/platform/web_feature.mojom
index d4cb9fd..cc6c1d5b 100644
--- a/third_party/blink/public/platform/web_feature.mojom
+++ b/third_party/blink/public/platform/web_feature.mojom
@@ -2225,6 +2225,10 @@
   kV8UserActivation_IsActive_AttributeGetter = 2786,
   kTextEncoderEncodeInto = 2787,
   kInvalidBasicCardMethodData = 2788,
+  kClientHintsUA = 2789,
+  kClientHintsUAArch = 2790,
+  kClientHintsUAPlatform = 2791,
+  kClientHintsUAModel = 2792,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots.
diff --git a/third_party/blink/public/platform/web_media_player_client.h b/third_party/blink/public/platform/web_media_player_client.h
index e4cab565..571e6da4 100644
--- a/third_party/blink/public/platform/web_media_player_client.h
+++ b/third_party/blink/public/platform/web_media_player_client.h
@@ -46,8 +46,6 @@
 class WebMediaSource;
 class WebRemotePlaybackClient;
 
-enum class WebRemotePlaybackAvailability;
-
 class BLINK_PLATFORM_EXPORT WebMediaPlayerClient {
  public:
   enum VideoTrackKind {
@@ -93,12 +91,6 @@
   virtual void RemoveTextTrack(WebInbandTextTrack*) = 0;
   virtual void MediaSourceOpened(WebMediaSource*) = 0;
   virtual void RequestSeek(double) = 0;
-  virtual void RemoteRouteAvailabilityChanged(
-      WebRemotePlaybackAvailability) = 0;
-  virtual void ConnectedToRemoteDevice() = 0;
-  virtual void DisconnectedFromRemoteDevice() = 0;
-  virtual void CancelledRemotePlaybackRequest() = 0;
-  virtual void RemotePlaybackStarted() = 0;
   virtual void RemotePlaybackCompatibilityChanged(const WebURL&,
                                                   bool is_compatible) = 0;
 
diff --git a/third_party/blink/public/web/web_render_theme.h b/third_party/blink/public/web/web_render_theme.h
index 7a1e0af..210239f0 100644
--- a/third_party/blink/public/web/web_render_theme.h
+++ b/third_party/blink/public/web/web_render_theme.h
@@ -42,6 +42,10 @@
 
 BLINK_EXPORT void SetFocusRingColor(SkColor);
 
+BLINK_EXPORT void SetMinimumStrokeWidthForFocusRing(float);
+
+BLINK_EXPORT void SetIsFocusRingOutset(bool);
+
 }  // namespace blink
 
 #endif
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc b/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc
index c345ace..ef4641f 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc
@@ -66,6 +66,7 @@
 #include "third_party/blink/renderer/platform/bindings/v8_private_property.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
+#include "third_party/blink/renderer/platform/scheduler/public/cooperative_scheduling_manager.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
diff --git a/third_party/blink/renderer/core/animation/non_interpolable_value.h b/third_party/blink/renderer/core/animation/non_interpolable_value.h
index 271eee8c..a570f8aa8 100644
--- a/third_party/blink/renderer/core/animation/non_interpolable_value.h
+++ b/third_party/blink/renderer/core/animation/non_interpolable_value.h
@@ -29,12 +29,11 @@
 #define DEFINE_NON_INTERPOLABLE_VALUE_TYPE(T) \
   NonInterpolableValue::Type T::static_type_ = &T::static_type_
 
-#define DEFINE_NON_INTERPOLABLE_VALUE_TYPE_CASTS(T)               \
-  inline bool Is##T(const NonInterpolableValue* value) {          \
-    return !value || value->GetType() == T::static_type_;         \
-  }                                                               \
-  DEFINE_TYPE_CASTS(T, NonInterpolableValue, value, Is##T(value), \
-                    Is##T(&value));
+#define DEFINE_NON_INTERPOLABLE_VALUE_TYPE_CASTS(T)       \
+  inline bool Is##T(const NonInterpolableValue* value) {  \
+    return !value || value->GetType() == T::static_type_; \
+  }                                                       \
+  DEFINE_TYPE_CASTS(T, NonInterpolableValue, value, Is##T(value), Is##T(&value))
 
 }  // namespace blink
 
diff --git a/third_party/blink/renderer/core/css/resolver/viewport_style_resolver.cc b/third_party/blink/renderer/core/css/resolver/viewport_style_resolver.cc
index 058dfd6d..142e2b9 100644
--- a/third_party/blink/renderer/core/css/resolver/viewport_style_resolver.cc
+++ b/third_party/blink/renderer/core/css/resolver/viewport_style_resolver.cc
@@ -318,7 +318,7 @@
     float scaled_value =
         document_->GetPage()->GetChromeClient().WindowToViewportScalar(
             result.GetFloatValue());
-    result.SetValue(scaled_value);
+    result = Length::Fixed(scaled_value);
   }
   return result;
 }
diff --git a/third_party/blink/renderer/core/editing/iterators/text_iterator_behavior.h b/third_party/blink/renderer/core/editing/iterators/text_iterator_behavior.h
index 59eb8df..ce189732 100644
--- a/third_party/blink/renderer/core/editing/iterators/text_iterator_behavior.h
+++ b/third_party/blink/renderer/core/editing/iterators/text_iterator_behavior.h
@@ -7,10 +7,13 @@
 
 #include "base/macros.h"
 #include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 
 namespace blink {
 
 class CORE_EXPORT TextIteratorBehavior final {
+  DISALLOW_NEW();
+
  public:
   class CORE_EXPORT Builder;
 
@@ -103,6 +106,8 @@
 };
 
 class CORE_EXPORT TextIteratorBehavior::Builder final {
+  STACK_ALLOCATED();
+
  public:
   explicit Builder(const TextIteratorBehavior&);
   Builder();
diff --git a/third_party/blink/renderer/core/editing/iterators/text_searcher_icu.h b/third_party/blink/renderer/core/editing/iterators/text_searcher_icu.h
index 1e6cdf1..071f02f 100644
--- a/third_party/blink/renderer/core/editing/iterators/text_searcher_icu.h
+++ b/third_party/blink/renderer/core/editing/iterators/text_searcher_icu.h
@@ -8,6 +8,7 @@
 #include "base/macros.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/editing/finder/find_options.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_view.h"
 #include "third_party/blink/renderer/platform/wtf/text/unicode.h"
 
@@ -21,6 +22,8 @@
 };
 
 class CORE_EXPORT TextSearcherICU {
+  DISALLOW_NEW();
+
  public:
   TextSearcherICU();
   ~TextSearcherICU();
diff --git a/third_party/blink/renderer/core/editing/serializers/markup_accumulator.cc b/third_party/blink/renderer/core/editing/serializers/markup_accumulator.cc
index 2de2f8a..660986c1 100644
--- a/third_party/blink/renderer/core/editing/serializers/markup_accumulator.cc
+++ b/third_party/blink/renderer/core/editing/serializers/markup_accumulator.cc
@@ -66,8 +66,8 @@
   }
 
   // https://w3c.github.io/DOM-Parsing/#dfn-recording-the-namespace-information
-  void RecordNamespaceInformation(const Element& element) {
-    local_default_namespace_ = AtomicString();
+  AtomicString RecordNamespaceInformation(const Element& element) {
+    AtomicString local_default_namespace;
     // 2. For each attribute attr in element's attributes, in the order they are
     // specified in the element's attribute list:
     for (const auto& attr : element.Attributes()) {
@@ -79,11 +79,13 @@
         // declaration. Set the default namespace attr value to attr's value
         // and stop running these steps, returning to Main to visit the next
         // attribute.
-        local_default_namespace_ = attr.Value();
+        local_default_namespace = attr.Value();
       } else if (attr.Prefix() == g_xmlns_atom) {
         Add(attr.Prefix() ? attr.LocalName() : g_empty_atom, attr.Value());
       }
     }
+    // 3. Return the value of default namespace attr value.
+    return local_default_namespace;
   }
 
   AtomicString LookupNamespaceURI(const AtomicString& prefix) const {
@@ -95,16 +97,13 @@
     context_namespace_ = context_ns;
   }
 
-  const AtomicString& LocalDefaultNamespace() const {
-    return local_default_namespace_;
-  }
-
-  void InheritLocalDefaultNamespace() {
-    if (!local_default_namespace_)
+  void InheritLocalDefaultNamespace(
+      const AtomicString& local_default_namespace) {
+    if (!local_default_namespace)
       return;
-    SetContextNamespace(local_default_namespace_.IsEmpty()
+    SetContextNamespace(local_default_namespace.IsEmpty()
                             ? g_null_atom
-                            : local_default_namespace_);
+                            : local_default_namespace);
   }
 
   const Vector<AtomicString> PrefixList(const AtomicString& ns) const {
@@ -122,9 +121,18 @@
 
   // https://w3c.github.io/DOM-Parsing/#dfn-context-namespace
   AtomicString context_namespace_;
+};
 
-  // https://w3c.github.io/DOM-Parsing/#dfn-local-default-namespace
-  AtomicString local_default_namespace_;
+// This stores values used to serialize an element. The values are not
+// inherited to child node serialization.
+class MarkupAccumulator::ElementSerializationData final {
+  STACK_ALLOCATED();
+
+ public:
+  // https://w3c.github.io/DOM-Parsing/#dfn-ignore-namespace-definition-attribute
+  bool ignore_namespace_definition_attribute_ = false;
+
+  AtomicString serialized_prefix_;
 };
 
 MarkupAccumulator::MarkupAccumulator(EAbsoluteURLs resolve_urls_method,
@@ -174,10 +182,10 @@
 }
 
 AtomicString MarkupAccumulator::AppendElement(const Element& element) {
-  AtomicString prefix = element.prefix();
+  const ElementSerializationData data = AppendStartTagOpen(element);
   if (SerializeAsHTMLDocument(element)) {
     // https://html.spec.whatwg.org/C/#html-fragment-serialisation-algorithm
-    AppendStartTagOpen(element);
+
     AttributeCollection attributes = element.Attributes();
     // 3.2. Element: If current node's is value is not null, and the
     // element does not have an is attribute in its attribute list, ...
@@ -191,13 +199,9 @@
     }
   } else {
     // https://w3c.github.io/DOM-Parsing/#xml-serializing-an-element-node
-    namespace_stack_.back().RecordNamespaceInformation(element);
-    auto pair = AppendStartTagOpen(element);
-    const bool ignore_namespace_definition_attribute = pair.first;
-    prefix = pair.second;
 
     for (const auto& attribute : element.Attributes()) {
-      if (ignore_namespace_definition_attribute &&
+      if (data.ignore_namespace_definition_attribute_ &&
           attribute.NamespaceURI() == xmlns_names::kNamespaceURI &&
           attribute.Prefix().IsEmpty())
         continue;
@@ -210,14 +214,16 @@
   AppendCustomAttributes(element);
 
   AppendStartTagClose(element);
-  return prefix;
+  return data.serialized_prefix_;
 }
 
-std::pair<bool, AtomicString> MarkupAccumulator::AppendStartTagOpen(
-    const Element& element) {
+MarkupAccumulator::ElementSerializationData
+MarkupAccumulator::AppendStartTagOpen(const Element& element) {
+  ElementSerializationData data;
+  data.serialized_prefix_ = element.prefix();
   if (SerializeAsHTMLDocument(element)) {
     formatter_.AppendStartTagOpen(markup_, element);
-    return std::make_pair(false, element.prefix());
+    return data;
   }
 
   // https://w3c.github.io/DOM-Parsing/#xml-serializing-an-element-node
@@ -226,11 +232,11 @@
 
   // 5. Let ignore namespace definition attribute be a boolean flag with value
   // false.
-  bool ignore_namespace_definition_attribute = false;
+  data.ignore_namespace_definition_attribute_ = false;
   // 8. Let local default namespace be the result of recording the namespace
   // information for node given map and local prefixes map.
   AtomicString local_default_namespace =
-      namespace_stack_.back().LocalDefaultNamespace();
+      namespace_context.RecordNamespaceInformation(element);
   // 9. Let inherited ns be a copy of namespace.
   AtomicString inherited_ns = namespace_context.ContextNamespace();
   // 10. Let ns be the value of node's namespaceURI attribute.
@@ -240,13 +246,15 @@
   if (inherited_ns == ns) {
     // 11.1. If local default namespace is not null, then set ignore namespace
     // definition attribute to true.
-    ignore_namespace_definition_attribute = !local_default_namespace.IsNull();
+    data.ignore_namespace_definition_attribute_ =
+        !local_default_namespace.IsNull();
     // 11.3. Otherwise, append to qualified name the value of node's
     // localName. The node's prefix if it exists, is dropped.
 
     // 11.4. Append the value of qualified name to markup.
     formatter_.AppendStartTagOpen(markup_, g_null_atom, element.localName());
-    return std::make_pair(ignore_namespace_definition_attribute, g_null_atom);
+    data.serialized_prefix_ = g_null_atom;
+    return data;
   }
 
   // 12. Otherwise, inherited ns is not equal to ns (the node's own namespace is
@@ -267,6 +275,7 @@
     // 12.4.3. Append the value of qualified name to markup.
     formatter_.AppendStartTagOpen(markup_, candidate_prefix,
                                   element.localName());
+    data.serialized_prefix_ = candidate_prefix;
     // 12.4.2. If the local default namespace is not null (there exists a
     // locally-defined default namespace declaration attribute) and its value is
     // not the XML namespace, then let inherited ns get the value of local
@@ -274,10 +283,8 @@
     // in which case let it get null (the context namespace is changed to the
     // declared default, rather than this node's own namespace).
     if (local_default_namespace != xml_names::kNamespaceURI)
-      namespace_context.InheritLocalDefaultNamespace();
-
-    return std::make_pair(ignore_namespace_definition_attribute,
-                          candidate_prefix);
+      namespace_context.InheritLocalDefaultNamespace(local_default_namespace);
+    return data;
   }
 
   // 12.5. Otherwise, if prefix is not null, then:
@@ -295,21 +302,22 @@
     // COLON), and node's localName.
     // 12.5.4. Append the value of qualified name to markup.
     formatter_.AppendStartTagOpen(markup_, prefix, element.localName());
+    data.serialized_prefix_ = prefix;
     // 12.5.5. Append the following to markup, in the order listed:
     MarkupFormatter::AppendAttribute(markup_, g_xmlns_atom, prefix, ns, false);
     // 12.5.5.7. If local default namespace is not null (there exists a
     // locally-defined default namespace declaration attribute), then let
     // inherited ns get the value of local default namespace unless the local
     // default namespace is the empty string in which case let it get null.
-    namespace_context.InheritLocalDefaultNamespace();
-    return std::make_pair(ignore_namespace_definition_attribute, prefix);
+    namespace_context.InheritLocalDefaultNamespace(local_default_namespace);
+    return data;
   }
 
   // 12.6. Otherwise, if local default namespace is null, or local default
   // namespace is not null and its value is not equal to ns, then:
   if (local_default_namespace.IsNull() || local_default_namespace != ns) {
     // 12.6.1. Set the ignore namespace definition attribute flag to true.
-    ignore_namespace_definition_attribute = true;
+    data.ignore_namespace_definition_attribute_ = true;
     // 12.6.3. Let the value of inherited ns be ns.
     namespace_context.SetContextNamespace(ns);
     // 12.6.4. Append the value of qualified name to markup.
@@ -317,7 +325,7 @@
     // 12.6.5. Append the following to markup, in the order listed:
     MarkupFormatter::AppendAttribute(markup_, g_null_atom, g_xmlns_atom, ns,
                                      false);
-    return std::make_pair(ignore_namespace_definition_attribute, g_null_atom);
+    return data;
   }
 
   // 12.7. Otherwise, the node has a local default namespace that matches
@@ -326,7 +334,7 @@
   DCHECK_EQ(local_default_namespace, ns);
   namespace_context.SetContextNamespace(ns);
   formatter_.AppendStartTagOpen(markup_, element);
-  return std::make_pair(ignore_namespace_definition_attribute, g_null_atom);
+  return data;
 }
 
 void MarkupAccumulator::AppendStartTagClose(const Element& element) {
diff --git a/third_party/blink/renderer/core/editing/serializers/markup_accumulator.h b/third_party/blink/renderer/core/editing/serializers/markup_accumulator.h
index ebd2895..0294f54 100644
--- a/third_party/blink/renderer/core/editing/serializers/markup_accumulator.h
+++ b/third_party/blink/renderer/core/editing/serializers/markup_accumulator.h
@@ -68,11 +68,13 @@
   void AppendString(const String&);
   // Serialize a Node, without its children and its end tag.
   void AppendStartMarkup(const Node&);
-  // Returns the pair of 'ignore namespace definition attribute' flag and the
-  // serialized prefix.
+
+  class ElementSerializationData;
+  // Returns 'ignore namespace definition attribute' flag and the serialized
+  // prefix.
   // If the flag is true, we should not serialize xmlns="..." on the element.
   // The prefix should be used in end tag serialization.
-  std::pair<bool, AtomicString> AppendStartTagOpen(const Element&);
+  ElementSerializationData AppendStartTagOpen(const Element&);
   void AppendStartTagClose(const Element&);
   void AppendNamespace(const AtomicString& prefix,
                        const AtomicString& namespace_uri);
diff --git a/third_party/blink/renderer/core/events/current_input_event.h b/third_party/blink/renderer/core/events/current_input_event.h
index 149b6a43..3820049 100644
--- a/third_party/blink/renderer/core/events/current_input_event.h
+++ b/third_party/blink/renderer/core/events/current_input_event.h
@@ -6,12 +6,15 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_EVENTS_CURRENT_INPUT_EVENT_H_
 
 #include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 
 namespace blink {
 
 class WebInputEvent;
 
 class CORE_EXPORT CurrentInputEvent {
+  STATIC_ONLY(CurrentInputEvent);
+
  public:
   // Gets the "current" input event - event that is currently being processed by
   // either blink::WebViewImpl::HandleInputEventInternal or by
diff --git a/third_party/blink/renderer/core/events/message_event.h b/third_party/blink/renderer/core/events/message_event.h
index 40db582..6d88a50 100644
--- a/third_party/blink/renderer/core/events/message_event.h
+++ b/third_party/blink/renderer/core/events/message_event.h
@@ -39,6 +39,7 @@
 #include "third_party/blink/renderer/core/fileapi/blob.h"
 #include "third_party/blink/renderer/core/messaging/message_port.h"
 #include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/compiler.h"
 
 namespace blink {
@@ -209,6 +210,8 @@
 
  private:
   class V8GCAwareString final {
+    DISALLOW_NEW();
+
    public:
     V8GCAwareString() = default;
     V8GCAwareString(const String&);
diff --git a/third_party/blink/renderer/core/exported/web_render_theme.cc b/third_party/blink/renderer/core/exported/web_render_theme.cc
index 4213986..dff6608 100644
--- a/third_party/blink/renderer/core/exported/web_render_theme.cc
+++ b/third_party/blink/renderer/core/exported/web_render_theme.cc
@@ -44,4 +44,12 @@
   LayoutTheme::GetTheme().SetCustomFocusRingColor(color);
 }
 
+void SetMinimumStrokeWidthForFocusRing(float stroke_width) {
+  LayoutTheme::GetTheme().SetMinimumStrokeWidthForFocusRing(stroke_width);
+}
+
+void SetIsFocusRingOutset(bool is_outset) {
+  LayoutTheme::GetTheme().SetIsFocusRingOutset(is_outset);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/frame/frame_test_helpers.h b/third_party/blink/renderer/core/frame/frame_test_helpers.h
index 7d0abe63..1eb3209 100644
--- a/third_party/blink/renderer/core/frame/frame_test_helpers.h
+++ b/third_party/blink/renderer/core/frame/frame_test_helpers.h
@@ -60,6 +60,7 @@
 #include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/core/testing/use_mock_scrollbar_settings.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
 #define EXPECT_FLOAT_POINT_EQ(expected, actual)    \
@@ -176,6 +177,8 @@
 // A class that constructs and owns a LayerTreeView for blink
 // unit tests.
 class LayerTreeViewFactory {
+  DISALLOW_NEW();
+
  public:
   // Use this to make a LayerTreeView with a stub delegate.
   content::LayerTreeView* Initialize();
@@ -255,6 +258,8 @@
 // Convenience class for handling the lifetime of a WebView and its associated
 // mainframe in tests.
 class WebViewHelper {
+  USING_FAST_MALLOC(WebViewHelper);
+
  public:
   WebViewHelper();
   ~WebViewHelper();
diff --git a/third_party/blink/renderer/core/frame/fullscreen_controller.h b/third_party/blink/renderer/core/frame/fullscreen_controller.h
index ab6676c1..3bffc9a7 100644
--- a/third_party/blink/renderer/core/frame/fullscreen_controller.h
+++ b/third_party/blink/renderer/core/frame/fullscreen_controller.h
@@ -39,6 +39,7 @@
 #include "third_party/blink/renderer/platform/geometry/int_size.h"
 #include "third_party/blink/renderer/platform/graphics/color.h"
 #include "third_party/blink/renderer/platform/heap/persistent.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 
 namespace blink {
 
@@ -50,6 +51,8 @@
 // and out of fullscreen, including restoring scroll offset and scale after
 // exiting fullscreen. It is (indirectly) used by the Fullscreen class.
 class CORE_EXPORT FullscreenController {
+  USING_FAST_MALLOC(FullscreenController);
+
  public:
   static std::unique_ptr<FullscreenController> Create(WebViewImpl*);
 
diff --git a/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h b/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h
index 2d3be7e..a8145cdb 100644
--- a/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h
+++ b/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h
@@ -6,6 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_LOCAL_FRAME_UKM_AGGREGATOR_H_
 
 #include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 #include "third_party/blink/renderer/platform/wtf/time.h"
 
@@ -172,6 +173,8 @@
   // aggregator that created the scoped timer. It will also record an event
   // into the histogram counter.
   class CORE_EXPORT ScopedUkmHierarchicalTimer {
+    STACK_ALLOCATED();
+
    public:
     ScopedUkmHierarchicalTimer(ScopedUkmHierarchicalTimer&&);
     ~ScopedUkmHierarchicalTimer();
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.h b/third_party/blink/renderer/core/frame/local_frame_view.h
index 7cf80d8b..4eac4650 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.h
+++ b/third_party/blink/renderer/core/frame/local_frame_view.h
@@ -47,6 +47,7 @@
 #include "third_party/blink/renderer/platform/graphics/paint_invalidation_reason.h"
 #include "third_party/blink/renderer/platform/graphics/subtree_paint_property_update_reason.h"
 #include "third_party/blink/renderer/platform/timer.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 #include "third_party/skia/include/core/SkColor.h"
 
 namespace cc {
@@ -724,6 +725,8 @@
  private:
 #if DCHECK_IS_ON()
   class DisallowLayoutInvalidationScope {
+    STACK_ALLOCATED();
+
    public:
     DisallowLayoutInvalidationScope(LocalFrameView* view)
         : local_frame_view_(view) {
diff --git a/third_party/blink/renderer/core/frame/navigator.h b/third_party/blink/renderer/core/frame/navigator.h
index 3c049fc..16ed6e3 100644
--- a/third_party/blink/renderer/core/frame/navigator.h
+++ b/third_party/blink/renderer/core/frame/navigator.h
@@ -71,8 +71,12 @@
 
   String GetAcceptLanguages() override;
   UserAgentMetadata GetUserAgentMetadata() const override;
+  void SetUserAgentMetadataForTesting(UserAgentMetadata);
 
   void Trace(blink::Visitor*) override;
+
+ private:
+  UserAgentMetadata metadata_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/frame/opened_frame_tracker.h b/third_party/blink/renderer/core/frame/opened_frame_tracker.h
index b89c6069..60c44d6c 100644
--- a/third_party/blink/renderer/core/frame/opened_frame_tracker.h
+++ b/third_party/blink/renderer/core/frame/opened_frame_tracker.h
@@ -6,6 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_OPENED_FRAME_TRACKER_H_
 
 #include "base/macros.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/hash_set.h"
 
 namespace blink {
@@ -16,6 +17,8 @@
 // Due to layering restrictions, we need to hide the implementation, since
 // public/web/ cannot depend on wtf/.
 class OpenedFrameTracker {
+  USING_FAST_MALLOC(OpenedFrameTracker);
+
  public:
   OpenedFrameTracker();
   ~OpenedFrameTracker();
diff --git a/third_party/blink/renderer/core/html/media/html_media_element.cc b/third_party/blink/renderer/core/html/media/html_media_element.cc
index eecab41..7eed8bec 100644
--- a/third_party/blink/renderer/core/html/media/html_media_element.cc
+++ b/third_party/blink/renderer/core/html/media/html_media_element.cc
@@ -33,7 +33,6 @@
 #include "base/debug/crash_logging.h"
 #include "base/memory/ptr_util.h"
 #include "media/base/logging_override_if_enabled.h"
-#include "third_party/blink/public/platform/modules/remoteplayback/web_remote_playback_availability.h"
 #include "third_party/blink/public/platform/modules/remoteplayback/web_remote_playback_client.h"
 #include "third_party/blink/public/platform/modules/remoteplayback/web_remote_playback_state.h"
 #include "third_party/blink/public/platform/platform.h"
@@ -505,7 +504,6 @@
       should_perform_automatic_track_selection_(true),
       tracks_are_ready_(true),
       processing_preference_change_(false),
-      playing_remotely_(false),
       mostly_filling_viewport_(false),
       was_always_muted_(true),
       audio_tracks_(AudioTrackList::Create(*this)),
@@ -2519,21 +2517,6 @@
   UpdatePlayState();
 }
 
-void HTMLMediaElement::RequestRemotePlayback() {
-  if (GetWebMediaPlayer())
-    GetWebMediaPlayer()->RequestRemotePlayback();
-}
-
-void HTMLMediaElement::RequestRemotePlaybackControl() {
-  if (GetWebMediaPlayer())
-    GetWebMediaPlayer()->RequestRemotePlaybackControl();
-}
-
-void HTMLMediaElement::RequestRemotePlaybackStop() {
-  if (GetWebMediaPlayer())
-    GetWebMediaPlayer()->RequestRemotePlaybackStop();
-}
-
 void HTMLMediaElement::FlingingStarted() {
   if (GetWebMediaPlayer())
     GetWebMediaPlayer()->FlingingStarted();
@@ -3299,48 +3282,12 @@
   setCurrentTime(time);
 }
 
-void HTMLMediaElement::RemoteRouteAvailabilityChanged(
-    WebRemotePlaybackAvailability availability) {
-  // The new remote playback pipeline is using the Presentation API for
-  // remote playback device availability monitoring.
-  // This code is left as is because many tests depend on this path for the
-  // moment, but it will be cleaned up.
-  // TODO(https://crbug.com/927099): Clean up old discovery paths.
-  // TODO(https://crubg.com/927451): Remove test depencencies on
-  // RemoteRouteAvailabilityChanged
-
-  if (RemotePlaybackClient())
-    RemotePlaybackClient()->AvailabilityChanged(availability);
-}
-
 bool HTMLMediaElement::HasRemoteRoutes() const {
   // TODO(mlamouri): used by MediaControlsPainter; should be refactored out.
   return RemotePlaybackClient() &&
          RemotePlaybackClient()->RemotePlaybackAvailable();
 }
 
-void HTMLMediaElement::ConnectedToRemoteDevice() {
-  playing_remotely_ = true;
-  if (RemotePlaybackClient())
-    RemotePlaybackClient()->StateChanged(WebRemotePlaybackState::kConnecting);
-}
-
-void HTMLMediaElement::DisconnectedFromRemoteDevice() {
-  playing_remotely_ = false;
-  if (RemotePlaybackClient())
-    RemotePlaybackClient()->StateChanged(WebRemotePlaybackState::kDisconnected);
-}
-
-void HTMLMediaElement::CancelledRemotePlaybackRequest() {
-  if (RemotePlaybackClient())
-    RemotePlaybackClient()->PromptCancelled();
-}
-
-void HTMLMediaElement::RemotePlaybackStarted() {
-  if (RemotePlaybackClient())
-    RemotePlaybackClient()->StateChanged(WebRemotePlaybackState::kConnected);
-}
-
 void HTMLMediaElement::RemotePlaybackCompatibilityChanged(const WebURL& url,
                                                           bool is_compatible) {
   if (RemotePlaybackClient())
@@ -3549,10 +3496,6 @@
   pending_action_flags_ = 0;
   load_state_ = kWaitingForSource;
 
-  // We can't cast if we don't have a media player.
-  playing_remotely_ = false;
-  RemoteRouteAvailabilityChanged(WebRemotePlaybackAvailability::kUnknown);
-
   if (GetLayoutObject())
     GetLayoutObject()->SetShouldDoFullPaintInvalidation();
 }
@@ -3907,10 +3850,6 @@
     ClearMediaPlayerAndAudioSourceProviderClientWithoutLocking();
   }
 
-  // We haven't yet found out if any remote routes are available.
-  playing_remotely_ = false;
-  RemoteRouteAvailabilityChanged(WebRemotePlaybackAvailability::kUnknown);
-
   if (audio_source_node_)
     GetAudioSourceProvider().SetClient(audio_source_node_);
 }
diff --git a/third_party/blink/renderer/core/html/media/html_media_element.h b/third_party/blink/renderer/core/html/media/html_media_element.h
index 68807403..b785d9e 100644
--- a/third_party/blink/renderer/core/html/media/html_media_element.h
+++ b/third_party/blink/renderer/core/html/media/html_media_element.h
@@ -136,7 +136,6 @@
   void ScheduleTextTrackResourceLoad();
 
   bool HasRemoteRoutes() const;
-  bool IsPlayingRemotely() const { return playing_remotely_; }
 
   // error state
   MediaError* error() const;
@@ -197,9 +196,6 @@
   ScriptPromise playForBindings(ScriptState*);
   base::Optional<DOMExceptionCode> Play();
   void pause();
-  void RequestRemotePlayback();
-  void RequestRemotePlaybackControl();
-  void RequestRemotePlaybackStop();
   void FlingingStarted();
   void FlingingStopped();
 
@@ -423,11 +419,6 @@
   void RemoveTextTrack(WebInbandTextTrack*) final;
   void MediaSourceOpened(WebMediaSource*) final;
   void RequestSeek(double) final;
-  void RemoteRouteAvailabilityChanged(WebRemotePlaybackAvailability) final;
-  void ConnectedToRemoteDevice() final;
-  void DisconnectedFromRemoteDevice() final;
-  void CancelledRemotePlaybackRequest() final;
-  void RemotePlaybackStarted() final;
   void RemotePlaybackCompatibilityChanged(const WebURL&,
                                           bool is_compatible) final;
   void OnBecamePersistentVideo(bool) override {}
@@ -672,7 +663,6 @@
 
   bool tracks_are_ready_ : 1;
   bool processing_preference_change_ : 1;
-  bool playing_remotely_ : 1;
 
   // The following is always false unless viewport intersection monitoring is
   // turned on via ActivateViewportIntersectionMonitoring().
diff --git a/third_party/blink/renderer/core/html/media/remote_playback_controller.h b/third_party/blink/renderer/core/html/media/remote_playback_controller.h
index 88a9dc31..5bd208e 100644
--- a/third_party/blink/renderer/core/html/media/remote_playback_controller.h
+++ b/third_party/blink/renderer/core/html/media/remote_playback_controller.h
@@ -26,6 +26,10 @@
   virtual void AddObserver(RemotePlaybackObserver*) = 0;
   virtual void RemoveObserver(RemotePlaybackObserver*) = 0;
 
+  // Exposes simplified internal methods for testing purposes.
+  virtual void AvailabilityChangedForTesting(bool screen_is_available) = 0;
+  virtual void StateChangedForTesting(bool is_connected) = 0;
+
   void Trace(Visitor*) override;
 
  protected:
diff --git a/third_party/blink/renderer/core/html/media/remote_playback_observer.h b/third_party/blink/renderer/core/html/media/remote_playback_observer.h
index e9e7cc8..591eecc 100644
--- a/third_party/blink/renderer/core/html/media/remote_playback_observer.h
+++ b/third_party/blink/renderer/core/html/media/remote_playback_observer.h
@@ -7,7 +7,6 @@
 
 namespace blink {
 
-enum class WebRemotePlaybackAvailability;
 enum class WebRemotePlaybackState;
 
 // Interface to be implemented by objects that intend to be notified by remote
@@ -18,11 +17,6 @@
   // Called when the remote playback state is changed. The state is related to
   // the connection to a remote device.
   virtual void OnRemotePlaybackStateChanged(WebRemotePlaybackState) = 0;
-
-  // Called when the remote playback availability is changed. The availabality
-  // is realted to the presence of a compatible remote device on the network.
-  virtual void OnRemotePlaybackAvailabilityChanged(
-      WebRemotePlaybackAvailability) = 0;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/media/video_wake_lock.cc b/third_party/blink/renderer/core/html/media/video_wake_lock.cc
index d5dfeb9..a9e5da87 100644
--- a/third_party/blink/renderer/core/html/media/video_wake_lock.cc
+++ b/third_party/blink/renderer/core/html/media/video_wake_lock.cc
@@ -60,9 +60,6 @@
   Update();
 }
 
-void VideoWakeLock::OnRemotePlaybackAvailabilityChanged(
-    WebRemotePlaybackAvailability) {}
-
 void VideoWakeLock::Update() {
   bool should_be_active = ShouldBeActive();
   if (should_be_active == active_)
diff --git a/third_party/blink/renderer/core/html/media/video_wake_lock.h b/third_party/blink/renderer/core/html/media/video_wake_lock.h
index ee4ec11..bbe5b499 100644
--- a/third_party/blink/renderer/core/html/media/video_wake_lock.h
+++ b/third_party/blink/renderer/core/html/media/video_wake_lock.h
@@ -40,7 +40,6 @@
 
   // RemotePlaybackObserver implementation.
   void OnRemotePlaybackStateChanged(WebRemotePlaybackState) final;
-  void OnRemotePlaybackAvailabilityChanged(WebRemotePlaybackAvailability) final;
 
   bool active_for_tests() const { return active_; }
 
diff --git a/third_party/blink/renderer/core/inspector/devtools_session.cc b/third_party/blink/renderer/core/inspector/devtools_session.cc
index 3eb4292..a44df36 100644
--- a/third_party/blink/renderer/core/inspector/devtools_session.cc
+++ b/third_party/blink/renderer/core/inspector/devtools_session.cc
@@ -43,11 +43,16 @@
 }
 
 mojom::blink::DevToolsMessagePtr WrapMessage(
-    const protocol::ProtocolMessage& message) {
+    protocol::ProtocolMessage message) {
   auto result = mojom::blink::DevToolsMessage::New();
-  WTF::StringUTF8Adaptor adaptor(message.json);
-  result->data = mojo_base::BigBuffer(base::make_span(
-      reinterpret_cast<const uint8_t*>(adaptor.Data()), adaptor.length()));
+
+  if (message.json.IsEmpty()) {
+    result->data = std::move(message.binary);
+  } else {
+    WTF::StringUTF8Adaptor adaptor(message.json);
+    result->data = mojo_base::BigBuffer(base::make_span(
+        reinterpret_cast<const uint8_t*>(adaptor.Data()), adaptor.length()));
+  }
   return result;
 }
 
@@ -129,7 +134,8 @@
       inspector_backend_dispatcher_(new protocol::UberDispatcher(this)),
       session_state_(std::move(reattach_session_state)),
       v8_session_state_(kV8StateKey),
-      v8_session_state_json_(&v8_session_state_, /*default_value=*/String()) {
+      v8_session_state_json_(&v8_session_state_, /*default_value=*/String()),
+      uses_binary_protocol_(&v8_session_state_, false) {
   io_session_ =
       new IOSession(agent_->io_task_runner_, agent_->inspector_task_runner_,
                     WrapCrossThreadWeakPersistent(this), std::move(io_request));
@@ -203,7 +209,7 @@
                                                   std::vector<uint8_t> data) {
   bool binary_protocol = data.size() && data[0] == 0xD8;
   if (binary_protocol)
-    uses_binary_protocol_ = true;
+    uses_binary_protocol_.Set(true);
 
   // IOSession does not provide ordering guarantees relative to
   // Session, so a command may come to IOSession after Session is detached,
@@ -267,7 +273,8 @@
 void DevToolsSession::sendProtocolResponse(
     int call_id,
     std::unique_ptr<protocol::Serializable> message) {
-  SendProtocolResponse(call_id, message->serialize(uses_binary_protocol_));
+  SendProtocolResponse(call_id,
+                       message->serialize(uses_binary_protocol_.Get()));
 }
 
 void DevToolsSession::fallThrough(int call_id,
@@ -283,8 +290,8 @@
   // We can potentially avoid copies if WebString would convert to utf8 right
   // from StringView, but it uses StringImpl itself, so we don't create any
   // extra copies here.
-  SendProtocolResponse(
-      call_id, ToProtocolMessage(std::move(message), uses_binary_protocol_));
+  SendProtocolResponse(call_id, ToProtocolMessage(std::move(message),
+                                                  uses_binary_protocol_.Get()));
 }
 
 void DevToolsSession::SendProtocolResponse(
@@ -333,7 +340,7 @@
       serialized = ToProtocolMessage(std::move(v8_notification_), binary);
       v8_notification_.reset();
     }
-    return WrapMessage(serialized);
+    return WrapMessage(std::move(serialized));
   }
 
  private:
@@ -367,9 +374,10 @@
   if (v8_session_)
     v8_session_state_json_.Set(ToCoreString(v8_session_->stateJSON()));
   for (wtf_size_t i = 0; i < notification_queue_.size(); ++i) {
-    host_ptr_->DispatchProtocolNotification(
-        notification_queue_[i]->Serialize(uses_binary_protocol_),
-        session_state_.TakeUpdates());
+    auto serialized =
+        notification_queue_[i]->Serialize(uses_binary_protocol_.Get());
+    host_ptr_->DispatchProtocolNotification(std::move(serialized),
+                                            session_state_.TakeUpdates());
   }
   notification_queue_.clear();
 }
diff --git a/third_party/blink/renderer/core/inspector/devtools_session.h b/third_party/blink/renderer/core/inspector/devtools_session.h
index efd265c..388f64676 100644
--- a/third_party/blink/renderer/core/inspector/devtools_session.h
+++ b/third_party/blink/renderer/core/inspector/devtools_session.h
@@ -97,7 +97,7 @@
   Vector<std::unique_ptr<Notification>> notification_queue_;
   InspectorAgentState v8_session_state_;
   InspectorAgentState::String v8_session_state_json_;
-  bool uses_binary_protocol_ = false;
+  InspectorAgentState::Boolean uses_binary_protocol_;
 
   DISALLOW_COPY_AND_ASSIGN(DevToolsSession);
 };
diff --git a/third_party/blink/renderer/core/intersection_observer/intersection_observer.cc b/third_party/blink/renderer/core/intersection_observer/intersection_observer.cc
index c6579ce..62e6b14 100644
--- a/third_party/blink/renderer/core/intersection_observer/intersection_observer.cc
+++ b/third_party/blink/renderer/core/intersection_observer/intersection_observer.cc
@@ -223,10 +223,10 @@
       root_(root),
       thresholds_(thresholds),
       delay_(delay),
-      top_margin_(kFixed),
-      right_margin_(kFixed),
-      bottom_margin_(kFixed),
-      left_margin_(kFixed),
+      top_margin_(Length::Fixed(0)),
+      right_margin_(Length::Fixed(0)),
+      bottom_margin_(Length::Fixed(0)),
+      left_margin_(Length::Fixed(0)),
       root_is_implicit_(root ? 0 : 1),
       track_visibility_(track_visibility ? 1 : 0),
       track_fraction_of_root_(semantics == kFractionOfRoot),
diff --git a/third_party/blink/renderer/core/layout/layout_box.cc b/third_party/blink/renderer/core/layout/layout_box.cc
index 69022e8..4c08a81 100644
--- a/third_party/blink/renderer/core/layout/layout_box.cc
+++ b/third_party/blink/renderer/core/layout/layout_box.cc
@@ -3101,9 +3101,9 @@
     // will think we're wider than we actually are and calculate line sizes
     // wrong. See also https://drafts.csswg.org/css-flexbox/#auto-margins
     if (margin_start_length.IsAuto())
-      margin_start_length.SetValue(0);
+      margin_start_length = Length::Fixed(0);
     if (margin_end_length.IsAuto())
-      margin_end_length.SetValue(0);
+      margin_end_length = Length::Fixed(0);
   }
 
   LayoutUnit margin_start_width =
@@ -4100,9 +4100,9 @@
   // them (depending on the direction) is simply "0".
   if (parent->IsLayoutGrid() && parent == child->ContainingBlock()) {
     if (parent_direction == TextDirection::kLtr)
-      logical_left.SetValue(kFixed, 0);
+      logical_left = Length::Fixed(0);
     else
-      logical_right.SetValue(kFixed, 0);
+      logical_right = Length::Fixed(0);
     return;
   }
 
@@ -4138,7 +4138,7 @@
         }
       }
     }
-    logical_left.SetValue(kFixed, static_position);
+    logical_left = Length::Fixed(static_position);
   } else {
     LayoutBox* enclosing_box = child->Parent()->EnclosingBox();
     LayoutUnit static_position = child->Layer()->StaticInlinePosition() +
@@ -4176,7 +4176,7 @@
       if (curr == container_block)
         break;
     }
-    logical_right.SetValue(kFixed, static_position);
+    logical_right = Length::Fixed(static_position);
   }
 }
 
@@ -4602,7 +4602,7 @@
     static_logical_top -=
         ToLayoutBox(container_block)->LogicalTopScrollbarHeight();
   }
-  logical_top.SetValue(kFixed, static_logical_top);
+  logical_top = Length::Fixed(static_logical_top);
 }
 
 void LayoutBox::ComputePositionedLogicalHeight(
diff --git a/third_party/blink/renderer/core/layout/layout_replaced.cc b/third_party/blink/renderer/core/layout/layout_replaced.cc
index 0cbb932..9436cbdf 100644
--- a/third_party/blink/renderer/core/layout/layout_replaced.cc
+++ b/third_party/blink/renderer/core/layout/layout_replaced.cc
@@ -272,9 +272,9 @@
   // ---------------------------------------------------------------------------
   if (logical_left.IsAuto() || logical_right.IsAuto()) {
     if (margin_logical_left.IsAuto())
-      margin_logical_left.SetValue(kFixed, 0);
+      margin_logical_left = Length::Fixed(0);
     if (margin_logical_right.IsAuto())
-      margin_logical_right.SetValue(kFixed, 0);
+      margin_logical_right = Length::Fixed(0);
   }
 
   // ---------------------------------------------------------------------------
@@ -481,9 +481,9 @@
   // auto, but if only top is auto, this makes step 4 impossible.
   if (logical_top.IsAuto() || logical_bottom.IsAuto()) {
     if (margin_before.IsAuto())
-      margin_before.SetValue(kFixed, 0);
+      margin_before = Length::Fixed(0);
     if (margin_after.IsAuto())
-      margin_after.SetValue(kFixed, 0);
+      margin_after = Length::Fixed(0);
   }
 
   // ---------------------------------------------------------------------------
diff --git a/third_party/blink/renderer/core/layout/layout_theme.cc b/third_party/blink/renderer/core/layout/layout_theme.cc
index d7df2dfe..6202634a 100644
--- a/third_party/blink/renderer/core/layout/layout_theme.cc
+++ b/third_party/blink/renderer/core/layout/layout_theme.cc
@@ -838,6 +838,22 @@
   has_custom_focus_ring_color_ = true;
 }
 
+bool LayoutTheme::IsFocusRingOutset() const {
+  return is_focus_ring_outset_;
+}
+
+void LayoutTheme::SetIsFocusRingOutset(bool is_outset) {
+  is_focus_ring_outset_ = is_outset;
+}
+
+float LayoutTheme::MinimumStrokeWidthForFocusRing() const {
+  return minimum_width_for_focus_ring_;
+}
+
+void LayoutTheme::SetMinimumStrokeWidthForFocusRing(float stroke_width) {
+  minimum_width_for_focus_ring_ = stroke_width;
+}
+
 Color LayoutTheme::FocusRingColor() const {
   return has_custom_focus_ring_color_ ? custom_focus_ring_color_
                                       : GetTheme().PlatformFocusRingColor();
diff --git a/third_party/blink/renderer/core/layout/layout_theme.h b/third_party/blink/renderer/core/layout/layout_theme.h
index 4137b78..98fd6b9 100644
--- a/third_party/blink/renderer/core/layout/layout_theme.h
+++ b/third_party/blink/renderer/core/layout/layout_theme.h
@@ -153,6 +153,10 @@
   Color PlatformTextSearchHighlightColor(bool active_match) const;
   Color PlatformTextSearchColor(bool active_match) const;
 
+  bool IsFocusRingOutset() const;
+  void SetIsFocusRingOutset(bool is_outset);
+  float MinimumStrokeWidthForFocusRing() const;
+  void SetMinimumStrokeWidthForFocusRing(float stroke_width);
   Color FocusRingColor() const;
   virtual Color PlatformFocusRingColor() const { return Color(0, 0, 0); }
   void SetCustomFocusRingColor(const Color&);
@@ -300,6 +304,8 @@
   // implementation to hand back the appropriate platform theme.
   static LayoutTheme& NativeTheme();
 
+  bool is_focus_ring_outset_ = false;
+  float minimum_width_for_focus_ring_ = 1.0f;
   Color custom_focus_ring_color_;
   bool has_custom_focus_ring_color_;
   TimeDelta caret_blink_interval_ = TimeDelta::FromMilliseconds(500);
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h
index c74ae29..b6fcf9e0 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h
@@ -80,9 +80,7 @@
   NGInlineItemType Type() const { return static_cast<NGInlineItemType>(type_); }
   const char* NGInlineItemTypeToString(int val) const;
 
-  scoped_refptr<const ShapeResult> TextShapeResult() const {
-    return shape_result_;
-  }
+  const ShapeResult* TextShapeResult() const { return shape_result_.get(); }
   NGLayoutInlineShapeOptions ShapeOptions() const {
     return static_cast<NGLayoutInlineShapeOptions>(shape_options_);
   }
diff --git a/third_party/blink/renderer/core/layout/table_layout_algorithm_auto.cc b/third_party/blink/renderer/core/layout/table_layout_algorithm_auto.cc
index 102310bc..9612baa 100644
--- a/third_party/blink/renderer/core/layout/table_layout_algorithm_auto.cc
+++ b/third_party/blink/renderer/core/layout/table_layout_algorithm_auto.cc
@@ -89,9 +89,9 @@
           if (cell_logical_width.IsCalculated())
             cell_logical_width = Length();  // Make it Auto
           if (cell_logical_width.Value() > kCCellMaxWidth)
-            cell_logical_width.SetValue(kCCellMaxWidth);
+            cell_logical_width = Length::Fixed(kCCellMaxWidth);
           if (cell_logical_width.IsNegative())
-            cell_logical_width.SetValue(0);
+            cell_logical_width = Length::Fixed(0);
           switch (cell_logical_width.GetType()) {
             case kFixed:
               // ignore width=0
@@ -106,11 +106,11 @@
                   if ((logical_width > column_layout.logical_width.Value()) ||
                       ((column_layout.logical_width.Value() == logical_width) &&
                        (max_contributor == cell))) {
-                    column_layout.logical_width.SetValue(kFixed, logical_width);
+                    column_layout.logical_width = Length::Fixed(logical_width);
                     fixed_contributor = cell;
                   }
                 } else {
-                  column_layout.logical_width.SetValue(kFixed, logical_width);
+                  column_layout.logical_width = Length::Fixed(logical_width);
                   fixed_contributor = cell;
                 }
               }
@@ -478,11 +478,8 @@
             total_width -=
                 layout_struct_[pos].ClampedEffectiveMaxLogicalWidth();
             percent_missing -= percent;
-            if (percent > 0)
-              layout_struct_[pos].effective_logical_width.SetValue(kPercent,
-                                                                   percent);
-            else
-              layout_struct_[pos].effective_logical_width = Length();
+            layout_struct_[pos].effective_logical_width =
+                percent > 0 ? Length::Percent(percent) : Length();
           }
         }
       }
diff --git a/third_party/blink/renderer/core/layout/table_layout_algorithm_fixed.cc b/third_party/blink/renderer/core/layout/table_layout_algorithm_fixed.cc
index ab6ccb2..16ba913 100644
--- a/third_party/blink/renderer/core/layout/table_layout_algorithm_fixed.cc
+++ b/third_party/blink/renderer/core/layout/table_layout_algorithm_fixed.cc
@@ -160,7 +160,7 @@
       fixed_border_box_logical_width =
           cell->AdjustBorderBoxLogicalWidthForBoxSizing(logical_width.Value())
               .ToInt();
-      logical_width.SetValue(fixed_border_box_logical_width);
+      logical_width = Length::Fixed(fixed_border_box_logical_width);
     }
 
     unsigned used_span = 0;
diff --git a/third_party/blink/renderer/core/loader/base_fetch_context.cc b/third_party/blink/renderer/core/loader/base_fetch_context.cc
index 3082f4d..4d814a0 100644
--- a/third_party/blink/renderer/core/loader/base_fetch_context.cc
+++ b/third_party/blink/renderer/core/loader/base_fetch_context.cc
@@ -391,7 +391,7 @@
   // Loading of a subresource may be blocked by previews resource loading hints.
   if (GetPreviewsResourceLoadingHints() &&
       !GetPreviewsResourceLoadingHints()->AllowLoad(
-          url, resource_request.Priority())) {
+          type, url, resource_request.Priority())) {
     return ResourceRequestBlockedReason::kOther;
   }
 
diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context.cc b/third_party/blink/renderer/core/loader/frame_fetch_context.cc
index 7269fca..812e49a 100644
--- a/third_party/blink/renderer/core/loader/frame_fetch_context.cc
+++ b/third_party/blink/renderer/core/loader/frame_fetch_context.cc
@@ -106,6 +106,7 @@
 #include "third_party/blink/renderer/platform/network/network_state_notifier.h"
 #include "third_party/blink/renderer/platform/network/network_utils.h"
 #include "third_party/blink/renderer/platform/weborigin/scheme_registry.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
 namespace blink {
@@ -180,6 +181,7 @@
               const ClientHintsPreferences& client_hints_preferences,
               float device_pixel_ratio,
               const String& user_agent,
+              const UserAgentMetadata& user_agent_metadata,
               bool is_svg_image_chrome_client)
       : url(url),
         parent_security_origin(std::move(parent_security_origin)),
@@ -189,6 +191,7 @@
         client_hints_preferences(client_hints_preferences),
         device_pixel_ratio(device_pixel_ratio),
         user_agent(user_agent),
+        user_agent_metadata(user_agent_metadata),
         is_svg_image_chrome_client(is_svg_image_chrome_client) {}
 
   const KURL url;
@@ -199,6 +202,7 @@
   const ClientHintsPreferences client_hints_preferences;
   const float device_pixel_ratio;
   const String user_agent;
+  const UserAgentMetadata user_agent_metadata;
   const bool is_svg_image_chrome_client;
 
   void Trace(blink::Visitor* visitor) {
@@ -723,6 +727,33 @@
   if (!AllowScriptFromSourceWithoutNotifying(request.Url()))
     return;
 
+  // Sec-CH-UA is special: we always send the header to all origins that are
+  // eligible for client hints (e.g. secure transport, JavaScript enabled). We
+  // alter the header's value based on whether or not the site has opted into
+  // additional detail.
+  //
+  // https://github.com/WICG/ua-client-hints
+  blink::UserAgentMetadata ua = GetUserAgentMetadata();
+  if (RuntimeEnabledFeatures::UserAgentClientHintEnabled()) {
+    StringBuilder result;
+    result.Append(ua.brand.data());
+    if (!ua.version.empty()) {
+      result.Append(' ');
+      if (ShouldSendClientHint(mojom::WebClientHintsType::kUA,
+                               hints_preferences, enabled_hints)) {
+        result.Append(ua.version.data());
+      } else {
+        // TODO(mkwst): This should only send the major version, but we haven't
+        // piped that through yet.
+        result.Append(ua.version.data());
+      }
+    }
+    request.AddHTTPHeaderField(
+        blink::kClientHintsHeaderMapping[static_cast<size_t>(
+            mojom::WebClientHintsType::kUA)],
+        result.ToAtomicString());
+  }
+
   bool is_1p_origin = IsFirstPartyOrigin(request.Url());
 
   if (!base::FeatureList::IsEnabled(kAllowClientHintsToThirdParty) &&
@@ -830,6 +861,30 @@
             ->navigator()
             ->SerializeLanguagesForClientHintHeader());
   }
+
+  if (ShouldSendClientHint(mojom::WebClientHintsType::kUAArch,
+                           hints_preferences, enabled_hints)) {
+    request.AddHTTPHeaderField(
+        blink::kClientHintsHeaderMapping[static_cast<size_t>(
+            mojom::WebClientHintsType::kUAArch)],
+        AtomicString(ua.architecture.data()));
+  }
+
+  if (ShouldSendClientHint(mojom::WebClientHintsType::kUAPlatform,
+                           hints_preferences, enabled_hints)) {
+    request.AddHTTPHeaderField(
+        blink::kClientHintsHeaderMapping[static_cast<size_t>(
+            mojom::WebClientHintsType::kUAPlatform)],
+        AtomicString(ua.platform.data()));
+  }
+
+  if (ShouldSendClientHint(mojom::WebClientHintsType::kUAModel,
+                           hints_preferences, enabled_hints)) {
+    request.AddHTTPHeaderField(
+        blink::kClientHintsHeaderMapping[static_cast<size_t>(
+            mojom::WebClientHintsType::kUAModel)],
+        AtomicString(ua.model.data()));
+  }
 }
 
 void FrameFetchContext::PopulateResourceRequest(
@@ -1072,6 +1127,12 @@
   return GetFrame()->Loader().UserAgent();
 }
 
+UserAgentMetadata FrameFetchContext::GetUserAgentMetadata() const {
+  if (GetResourceFetcherProperties().IsDetached())
+    return frozen_state_->user_agent_metadata;
+  return GetLocalFrameClient()->UserAgentMetadata();
+}
+
 const ClientHintsPreferences FrameFetchContext::GetClientHintsPreferences()
     const {
   if (GetResourceFetcherProperties().IsDetached())
@@ -1114,13 +1175,15 @@
     frozen_state_ = MakeGarbageCollected<FrozenState>(
         Url(), GetParentSecurityOrigin(), GetContentSecurityPolicy(),
         GetSiteForCookies(), GetTopFrameOrigin(), GetClientHintsPreferences(),
-        GetDevicePixelRatio(), GetUserAgent(), IsSVGImageChromeClient());
+        GetDevicePixelRatio(), GetUserAgent(), GetUserAgentMetadata(),
+        IsSVGImageChromeClient());
   } else {
     // Some getters are unavailable in this case.
     frozen_state_ = MakeGarbageCollected<FrozenState>(
         NullURL(), GetParentSecurityOrigin(), GetContentSecurityPolicy(),
         GetSiteForCookies(), GetTopFrameOrigin(), GetClientHintsPreferences(),
-        GetDevicePixelRatio(), GetUserAgent(), IsSVGImageChromeClient());
+        GetDevicePixelRatio(), GetUserAgent(), GetUserAgentMetadata(),
+        IsSVGImageChromeClient());
   }
 
   frame_or_imported_document_ = nullptr;
diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context.h b/third_party/blink/renderer/core/loader/frame_fetch_context.h
index b7563821..6f9d676 100644
--- a/third_party/blink/renderer/core/loader/frame_fetch_context.h
+++ b/third_party/blink/renderer/core/loader/frame_fetch_context.h
@@ -60,6 +60,7 @@
 class ResourceResponse;
 class Settings;
 class WebContentSettingsClient;
+struct UserAgentMetadata;
 struct WebEnabledClientHints;
 
 class CORE_EXPORT FrameFetchContext final : public BaseFetchContext {
@@ -204,6 +205,7 @@
   WebContentSettingsClient* GetContentSettingsClient() const;
   Settings* GetSettings() const;
   String GetUserAgent() const;
+  UserAgentMetadata GetUserAgentMetadata() const;
   const ClientHintsPreferences GetClientHintsPreferences() const;
   float GetDevicePixelRatio() const;
   bool ShouldSendClientHint(mojom::WebClientHintsType,
diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc b/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc
index b2f9d7d..bb44e2e 100644
--- a/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc
+++ b/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc
@@ -643,6 +643,7 @@
                     bool is_present,
                     const char* header_value,
                     float width = 0) {
+    SCOPED_TRACE(testing::Message() << header_name);
     ClientHintsPreferences hints_preferences;
 
     FetchParameters::ResourceWidth resource_width;
@@ -822,6 +823,69 @@
   ExpectHeader("http://www.example.com/1.gif", "Sec-CH-Lang", false, "");
 }
 
+TEST_F(FrameFetchContextHintsTest, MonitorUAHints) {
+  // `Sec-CH-UA` is always sent for secure requests
+  ExpectHeader("https://www.example.com/1.gif", "Sec-CH-UA", true, "");
+  ExpectHeader("http://www.example.com/1.gif", "Sec-CH-UA", false, "");
+
+  // `Sec-CH-UA-*` requires opt-in.
+  ExpectHeader("https://www.example.com/1.gif", "Sec-CH-UA-Arch", false, "");
+  ExpectHeader("http://www.example.com/1.gif", "Sec-CH-UA-Arch", false, "");
+  ExpectHeader("https://www.example.com/1.gif", "Sec-CH-UA-Platform", false,
+               "");
+  ExpectHeader("http://www.example.com/1.gif", "Sec-CH-UA-Platform", false, "");
+  ExpectHeader("https://www.example.com/1.gif", "Sec-CH-UA-Model", false, "");
+  ExpectHeader("http://www.example.com/1.gif", "Sec-CH-UA-Model", false, "");
+
+  {
+    ClientHintsPreferences preferences;
+    preferences.SetShouldSendForTesting(mojom::WebClientHintsType::kUAArch);
+    document->GetFrame()->GetClientHintsPreferences().UpdateFrom(preferences);
+
+    ExpectHeader("https://www.example.com/1.gif", "Sec-CH-UA-Arch", true, "");
+    ExpectHeader("https://www.example.com/1.gif", "Sec-CH-UA-Platform", false,
+                 "");
+    ExpectHeader("https://www.example.com/1.gif", "Sec-CH-UA-Model", false, "");
+
+    ExpectHeader("http://www.example.com/1.gif", "Sec-CH-UA-Arch", false, "");
+    ExpectHeader("http://www.example.com/1.gif", "Sec-CH-UA-Platform", false,
+                 "");
+    ExpectHeader("http://www.example.com/1.gif", "Sec-CH-UA-Model", false, "");
+  }
+
+  {
+    ClientHintsPreferences preferences;
+    preferences.SetShouldSendForTesting(mojom::WebClientHintsType::kUAPlatform);
+    document->GetFrame()->GetClientHintsPreferences().UpdateFrom(preferences);
+
+    ExpectHeader("https://www.example.com/1.gif", "Sec-CH-UA-Arch", false, "");
+    ExpectHeader("https://www.example.com/1.gif", "Sec-CH-UA-Platform", true,
+                 "");
+    ExpectHeader("https://www.example.com/1.gif", "Sec-CH-UA-Model", false, "");
+
+    ExpectHeader("http://www.example.com/1.gif", "Sec-CH-UA-Arch", false, "");
+    ExpectHeader("http://www.example.com/1.gif", "Sec-CH-UA-Platform", false,
+                 "");
+    ExpectHeader("http://www.example.com/1.gif", "Sec-CH-UA-Model", false, "");
+  }
+
+  {
+    ClientHintsPreferences preferences;
+    preferences.SetShouldSendForTesting(mojom::WebClientHintsType::kUAModel);
+    document->GetFrame()->GetClientHintsPreferences().UpdateFrom(preferences);
+
+    ExpectHeader("https://www.example.com/1.gif", "Sec-CH-UA-Arch", false, "");
+    ExpectHeader("https://www.example.com/1.gif", "Sec-CH-UA-Platform", false,
+                 "");
+    ExpectHeader("https://www.example.com/1.gif", "Sec-CH-UA-Model", true, "");
+
+    ExpectHeader("http://www.example.com/1.gif", "Sec-CH-UA-Arch", false, "");
+    ExpectHeader("http://www.example.com/1.gif", "Sec-CH-UA-Platform", false,
+                 "");
+    ExpectHeader("http://www.example.com/1.gif", "Sec-CH-UA-Model", false, "");
+  }
+}
+
 TEST_F(FrameFetchContextHintsTest, MonitorAllHints) {
   ExpectHeader("https://www.example.com/1.gif", "Device-Memory", false, "");
   ExpectHeader("https://www.example.com/1.gif", "DPR", false, "");
@@ -831,6 +895,13 @@
   ExpectHeader("https://www.example.com/1.gif", "downlink", false, "");
   ExpectHeader("https://www.example.com/1.gif", "ect", false, "");
   ExpectHeader("https://www.example.com/1.gif", "Sec-CH-Lang", false, "");
+  ExpectHeader("https://www.example.com/1.gif", "Sec-CH-UA-Arch", false, "");
+  ExpectHeader("https://www.example.com/1.gif", "Sec-CH-UA-Platform", false,
+               "");
+  ExpectHeader("https://www.example.com/1.gif", "Sec-CH-UA-Model", false, "");
+
+  // `Sec-CH-UA` is special.
+  ExpectHeader("https://www.example.com/1.gif", "Sec-CH-UA", true, "");
 
   ClientHintsPreferences preferences;
   preferences.SetShouldSendForTesting(mojom::WebClientHintsType::kDeviceMemory);
@@ -843,6 +914,10 @@
   preferences.SetShouldSendForTesting(mojom::WebClientHintsType::kDownlink);
   preferences.SetShouldSendForTesting(mojom::WebClientHintsType::kEct);
   preferences.SetShouldSendForTesting(mojom::WebClientHintsType::kLang);
+  preferences.SetShouldSendForTesting(mojom::WebClientHintsType::kUA);
+  preferences.SetShouldSendForTesting(mojom::WebClientHintsType::kUAArch);
+  preferences.SetShouldSendForTesting(mojom::WebClientHintsType::kUAPlatform);
+  preferences.SetShouldSendForTesting(mojom::WebClientHintsType::kUAModel);
   ApproximatedDeviceMemory::SetPhysicalMemoryMBForTesting(4096);
   document->GetFrame()->GetClientHintsPreferences().UpdateFrom(preferences);
   ExpectHeader("https://www.example.com/1.gif", "Device-Memory", true, "4");
@@ -854,6 +929,11 @@
   ExpectHeader("https://www.example.com/1.gif", "Sec-CH-Lang", true,
                "\"en\", \"de\", \"fr\"");
 
+  ExpectHeader("https://www.example.com/1.gif", "Sec-CH-UA", true, "");
+  ExpectHeader("https://www.example.com/1.gif", "Sec-CH-UA-Arch", true, "");
+  ExpectHeader("https://www.example.com/1.gif", "Sec-CH-UA-Platform", true, "");
+  ExpectHeader("https://www.example.com/1.gif", "Sec-CH-UA-Model", true, "");
+
   // Value of network quality client hints may vary, so only check if the
   // header is present and the values are non-negative/non-empty.
   bool conversion_ok = false;
diff --git a/third_party/blink/renderer/core/loader/previews_resource_loading_hints.cc b/third_party/blink/renderer/core/loader/previews_resource_loading_hints.cc
index a8ff441..68c172e 100644
--- a/third_party/blink/renderer/core/loader/previews_resource_loading_hints.cc
+++ b/third_party/blink/renderer/core/loader/previews_resource_loading_hints.cc
@@ -4,9 +4,11 @@
 
 #include "third_party/blink/renderer/core/loader/previews_resource_loading_hints.h"
 
+#include "base/metrics/field_trial_params.h"
 #include "base/metrics/histogram_macros.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
 #include "services/metrics/public/cpp/ukm_recorder.h"
+#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/inspector/console_message.h"
@@ -14,6 +16,7 @@
 #include "third_party/blink/renderer/core/loader/document_loader.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 namespace blink {
 
@@ -48,16 +51,44 @@
   subresource_patterns_to_block_usage_.assign(
       subresource_patterns_to_block.size(), false);
   blocked_resource_load_priority_counts_.fill(0);
+
+  // Populate which specific resource types are eligible for blocking.
+  // Certain resource types are blocked by default since their blocking
+  // is currently verified by the server verification pipeline. Note that
+  // the blocking of these resource types can be overridden using field trial.
+  block_resource_type_[static_cast<int>(ResourceType::kCSSStyleSheet)] = true;
+  block_resource_type_[static_cast<int>(ResourceType::kScript)] = true;
+  block_resource_type_[static_cast<int>(ResourceType::kRaw)] = true;
+  for (int i = 0; i < static_cast<int>(ResourceType::kLast) + 1; ++i) {
+    // Parameter names are of format: "block_resource_type_%d". The value
+    // should be either "true" or "false".
+    block_resource_type_[i] = base::GetFieldTrialParamByFeatureAsBool(
+        features::kPreviewsResourceLoadingHintsSpecificResourceTypes,
+        String::Format("block_resource_type_%d", i).Ascii().data(),
+        block_resource_type_[i]);
+  }
+
+  // Ensure that the ResourceType enums have not changed. These should not be
+  // changed since the resource type integer values are used as field trial
+  // params.
+  static_assert(static_cast<int>(ResourceType::kImage) == 1 &&
+                    static_cast<int>(ResourceType::kCSSStyleSheet) == 2 &&
+                    static_cast<int>(ResourceType::kScript) == 3,
+                "ResourceType enums can't be changed");
 }
 
 PreviewsResourceLoadingHints::~PreviewsResourceLoadingHints() = default;
 
 bool PreviewsResourceLoadingHints::AllowLoad(
+    ResourceType type,
     const KURL& resource_url,
     ResourceLoadPriority resource_load_priority) const {
   if (!resource_url.ProtocolIsInHTTPFamily())
     return true;
 
+  if (!block_resource_type_[static_cast<int>(type)])
+    return true;
+
   WTF::String resource_url_string = resource_url.GetString();
   resource_url_string = resource_url_string.Left(resource_url.PathEnd());
   bool allow_load = true;
diff --git a/third_party/blink/renderer/core/loader/previews_resource_loading_hints.h b/third_party/blink/renderer/core/loader/previews_resource_loading_hints.h
index 2ad7cc2..db3febb 100644
--- a/third_party/blink/renderer/core/loader/previews_resource_loading_hints.h
+++ b/third_party/blink/renderer/core/loader/previews_resource_loading_hints.h
@@ -7,9 +7,11 @@
 
 #include <vector>
 
+#include "base/feature_list.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/loader/fetch/resource.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_load_priority.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
@@ -38,9 +40,11 @@
       const std::vector<WTF::String>& subresource_patterns_to_block);
   ~PreviewsResourceLoadingHints();
 
-  // Returns true if load of resource with URL |resource_url| and priority
-  // |resource_load_priority| is allowed as per resource loading hints.
-  bool AllowLoad(const KURL& resource_url,
+  // Returns true if load of resource with type |type|, URL |resource_url|
+  // and priority |resource_load_priority| is allowed as per resource loading
+  // hints.
+  bool AllowLoad(ResourceType type,
+                 const KURL& resource_url,
                  ResourceLoadPriority resource_load_priority) const;
 
   virtual void Trace(blink::Visitor*);
@@ -64,6 +68,10 @@
   // be blocked.
   const std::vector<WTF::String> subresource_patterns_to_block_;
 
+  // True if resource blocking hints should apply to resource of a given type.
+  bool block_resource_type_[static_cast<int>(ResourceType::kLast) + 1] = {
+      false};
+
   // |subresource_patterns_to_block_usage_| records whether the pattern located
   // at the same index in |subresource_patterns_to_block_| was ever blocked.
   mutable std::vector<bool> subresource_patterns_to_block_usage_;
diff --git a/third_party/blink/renderer/core/loader/previews_resource_loading_hints_test.cc b/third_party/blink/renderer/core/loader/previews_resource_loading_hints_test.cc
index 4d5a001a..03869ad 100644
--- a/third_party/blink/renderer/core/loader/previews_resource_loading_hints_test.cc
+++ b/third_party/blink/renderer/core/loader/previews_resource_loading_hints_test.cc
@@ -8,15 +8,18 @@
 #include <vector>
 
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "components/ukm/test_ukm_recorder.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
 #include "services/metrics/public/cpp/ukm_recorder.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/loader/frame_loader.h"
 #include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
 #include "third_party/blink/renderer/core/testing/page_test_base.h"
 #include "third_party/blink/renderer/platform/geometry/int_size.h"
+#include "third_party/blink/renderer/platform/loader/fetch/resource.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_load_priority.h"
 #include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
@@ -42,7 +45,8 @@
   PreviewsResourceLoadingHints* hints = PreviewsResourceLoadingHints::Create(
       dummy_page_holder_->GetDocument(), ukm::UkmRecorder::GetNewSourceID(),
       subresources_to_block);
-  EXPECT_TRUE(hints->AllowLoad(KURL("https://www.example.com/"),
+  EXPECT_TRUE(hints->AllowLoad(ResourceType::kScript,
+                               KURL("https://www.example.com/"),
                                ResourceLoadPriority::kHighest));
 }
 
@@ -72,16 +76,24 @@
 
   for (const auto& test : tests) {
     base::HistogramTester histogram_tester;
+    // By default, resource blocking hints do not apply to images.
+    EXPECT_TRUE(hints->AllowLoad(ResourceType::kImage, test.url,
+                                 ResourceLoadPriority::kHighest));
+    // By default, resource blocking hints apply to CSS and Scripts.
     EXPECT_EQ(test.allow_load_expected,
-              hints->AllowLoad(test.url, ResourceLoadPriority::kHighest));
+              hints->AllowLoad(ResourceType::kCSSStyleSheet, test.url,
+                               ResourceLoadPriority::kHighest));
+    EXPECT_EQ(test.allow_load_expected,
+              hints->AllowLoad(ResourceType::kScript, test.url,
+                               ResourceLoadPriority::kHighest));
     histogram_tester.ExpectUniqueSample(
         "ResourceLoadingHints.ResourceLoadingBlocked",
-        !test.allow_load_expected, 1);
+        !test.allow_load_expected, 2);
     if (!test.allow_load_expected) {
       histogram_tester.ExpectUniqueSample(
           "ResourceLoadingHints.ResourceLoadingBlocked.ResourceLoadPriority."
           "Blocked",
-          ResourceLoadPriority::kHighest, 1);
+          ResourceLoadPriority::kHighest, 2);
       histogram_tester.ExpectTotalCount(
           "ResourceLoadingHints.ResourceLoadingBlocked.ResourceLoadPriority."
           "Allowed",
@@ -94,7 +106,7 @@
       histogram_tester.ExpectUniqueSample(
           "ResourceLoadingHints.ResourceLoadingBlocked.ResourceLoadPriority."
           "Allowed",
-          ResourceLoadPriority::kHighest, 1);
+          ResourceLoadPriority::kHighest, 2);
     }
   }
 }
@@ -128,7 +140,8 @@
 
   for (const auto& test : tests) {
     EXPECT_EQ(test.allow_load_expected,
-              hints->AllowLoad(test.url, ResourceLoadPriority::kHighest));
+              hints->AllowLoad(ResourceType::kScript, test.url,
+                               ResourceLoadPriority::kHighest));
   }
 }
 
@@ -158,7 +171,8 @@
   for (const auto& test : tests) {
     base::HistogramTester histogram_tester;
     EXPECT_EQ(test.allow_load_expected,
-              hints->AllowLoad(test.url, test.resource_load_priority));
+              hints->AllowLoad(ResourceType::kScript, test.url,
+                               test.resource_load_priority));
     histogram_tester.ExpectUniqueSample(
         "ResourceLoadingHints.ResourceLoadingBlocked",
         !test.allow_load_expected, 1);
@@ -235,7 +249,7 @@
   };
 
   for (const auto& resource_to_load : resources_to_load) {
-    hints->AllowLoad(resource_to_load.url,
+    hints->AllowLoad(ResourceType::kScript, resource_to_load.url,
                      resource_to_load.resource_load_priority);
   }
 
@@ -262,6 +276,122 @@
       entry, UkmEntry::kblocked_very_high_priorityName, 1);
 }
 
+// Test class that overrides field trial so that resource blocking hints apply
+// to images as well.
+class PreviewsResourceLoadingHintsTestBlockImages
+    : public PreviewsResourceLoadingHintsTest {
+ public:
+  PreviewsResourceLoadingHintsTestBlockImages() = default;
+
+  void SetUp() override {
+    std::map<std::string, std::string> feature_parameters;
+    feature_parameters["block_resource_type_1"] = "true";
+
+    scoped_feature_list_.InitAndEnableFeatureWithParameters(
+        blink::features::kPreviewsResourceLoadingHintsSpecificResourceTypes,
+        feature_parameters);
+  }
+
+ protected:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+TEST_F(PreviewsResourceLoadingHintsTestBlockImages,
+       OnePatternWithResourceSubtype) {
+  std::vector<WTF::String> subresources_to_block;
+  subresources_to_block.push_back("foo.jpg");
+
+  PreviewsResourceLoadingHints* hints = PreviewsResourceLoadingHints::Create(
+      dummy_page_holder_->GetDocument(), ukm::UkmRecorder::GetNewSourceID(),
+      subresources_to_block);
+
+  const struct {
+    KURL url;
+    bool allow_load_expected;
+  } tests[] = {
+      {KURL("https://www.example.com/"), true},
+      {KURL("https://www.example.com/foo.js"), true},
+      {KURL("https://www.example.com/foo.jpg"), false},
+      {KURL("https://www.example.com/pages/foo.jpg"), false},
+      {KURL("https://www.example.com/foobar.jpg"), true},
+      {KURL("https://www.example.com/barfoo.jpg"), false},
+      {KURL("http://www.example.com/foo.jpg"), false},
+      {KURL("http://www.example.com/foo.jpg?q=alpha"), false},
+      {KURL("http://www.example.com/bar.jpg?q=foo.jpg"), true},
+      {KURL("http://www.example.com/bar.jpg?q=foo.jpg#foo.jpg"), true},
+  };
+
+  for (const auto& test : tests) {
+    // By default, resource blocking hints do not apply to fonts.
+    EXPECT_TRUE(hints->AllowLoad(ResourceType::kFont, test.url,
+                                 ResourceLoadPriority::kHighest));
+    // Feature override should cause resource blocking hints to apply to images.
+    EXPECT_EQ(test.allow_load_expected,
+              hints->AllowLoad(ResourceType::kImage, test.url,
+                               ResourceLoadPriority::kHighest));
+    EXPECT_EQ(test.allow_load_expected,
+              hints->AllowLoad(ResourceType::kScript, test.url,
+                               ResourceLoadPriority::kHighest));
+  }
+}
+
+// Test class that overrides field trial so that resource blocking hints do not
+// apply to CSS.
+class PreviewsResourceLoadingHintsTestAllowCSS
+    : public PreviewsResourceLoadingHintsTestBlockImages {
+ public:
+  PreviewsResourceLoadingHintsTestAllowCSS() = default;
+
+  void SetUp() override {
+    std::map<std::string, std::string> feature_parameters;
+    feature_parameters["block_resource_type_2"] = "false";
+
+    scoped_feature_list_.InitAndEnableFeatureWithParameters(
+        blink::features::kPreviewsResourceLoadingHintsSpecificResourceTypes,
+        feature_parameters);
+  }
+};
+
+TEST_F(PreviewsResourceLoadingHintsTestAllowCSS,
+       OnePatternWithResourceSubtype) {
+  std::vector<WTF::String> subresources_to_block;
+  subresources_to_block.push_back("foo.jpg");
+
+  PreviewsResourceLoadingHints* hints = PreviewsResourceLoadingHints::Create(
+      dummy_page_holder_->GetDocument(), ukm::UkmRecorder::GetNewSourceID(),
+      subresources_to_block);
+
+  const struct {
+    KURL url;
+    bool allow_load_expected;
+  } tests[] = {
+      {KURL("https://www.example.com/"), true},
+      {KURL("https://www.example.com/foo.js"), true},
+      {KURL("https://www.example.com/foo.jpg"), false},
+      {KURL("https://www.example.com/pages/foo.jpg"), false},
+      {KURL("https://www.example.com/foobar.jpg"), true},
+      {KURL("https://www.example.com/barfoo.jpg"), false},
+      {KURL("http://www.example.com/foo.jpg"), false},
+      {KURL("http://www.example.com/foo.jpg?q=alpha"), false},
+      {KURL("http://www.example.com/bar.jpg?q=foo.jpg"), true},
+      {KURL("http://www.example.com/bar.jpg?q=foo.jpg#foo.jpg"), true},
+  };
+
+  for (const auto& test : tests) {
+    // Feature override should cause resource blocking hints to apply to only
+    // scripts.
+    EXPECT_TRUE(hints->AllowLoad(ResourceType::kFont, test.url,
+                                 ResourceLoadPriority::kHighest));
+    EXPECT_TRUE(hints->AllowLoad(ResourceType::kImage, test.url,
+                                 ResourceLoadPriority::kHighest));
+    EXPECT_TRUE(hints->AllowLoad(ResourceType::kCSSStyleSheet, test.url,
+                                 ResourceLoadPriority::kHighest));
+    EXPECT_EQ(test.allow_load_expected,
+              hints->AllowLoad(ResourceType::kScript, test.url,
+                               ResourceLoadPriority::kHighest));
+  }
+}
+
 }  // namespace
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/loader/private/frame_client_hints_preferences_context.cc b/third_party/blink/renderer/core/loader/private/frame_client_hints_preferences_context.cc
index 480d0344..0216216 100644
--- a/third_party/blink/renderer/core/loader/private/frame_client_hints_preferences_context.cc
+++ b/third_party/blink/renderer/core/loader/private/frame_client_hints_preferences_context.cc
@@ -22,6 +22,10 @@
     WebFeature::kClientHintsDownlink,
     WebFeature::kClientHintsEct,
     WebFeature::kClientHintsLang,
+    WebFeature::kClientHintsUA,
+    WebFeature::kClientHintsUAArch,
+    WebFeature::kClientHintsUAPlatform,
+    WebFeature::kClientHintsUAModel,
 };
 
 static_assert(static_cast<int>(mojom::WebClientHintsType::kMaxValue) + 1 ==
diff --git a/third_party/blink/renderer/core/paint/object_painter_base.cc b/third_party/blink/renderer/core/paint/object_painter_base.cc
index 929c97bc..78bfba6 100644
--- a/third_party/blink/renderer/core/paint/object_painter_base.cc
+++ b/third_party/blink/renderer/core/paint/object_painter_base.cc
@@ -499,9 +499,10 @@
 
   Color color = style.VisitedDependentColor(GetCSSPropertyOutlineColor());
   if (style.OutlineStyleIsAuto()) {
-    paint_info.context.DrawFocusRing(pixel_snapped_outline_rects,
-                                     style.GetOutlineStrokeWidthForFocusRing(),
-                                     style.OutlineOffset(), color);
+    paint_info.context.DrawFocusRing(
+        pixel_snapped_outline_rects, style.GetOutlineStrokeWidthForFocusRing(),
+        style.OutlineOffset(), color,
+        LayoutTheme::GetTheme().IsFocusRingOutset());
     return;
   }
 
diff --git a/third_party/blink/renderer/core/script/classic_script.cc b/third_party/blink/renderer/core/script/classic_script.cc
index 82eba9b..67f6888d 100644
--- a/third_party/blink/renderer/core/script/classic_script.cc
+++ b/third_party/blink/renderer/core/script/classic_script.cc
@@ -15,7 +15,7 @@
 }
 
 void ClassicScript::RunScript(LocalFrame* frame,
-                              const SecurityOrigin* security_origin) const {
+                              const SecurityOrigin* security_origin) {
   frame->GetScriptController().ExecuteScriptInMainWorld(
       GetScriptSourceCode(), BaseURL(), sanitize_script_errors_,
       FetchOptions());
diff --git a/third_party/blink/renderer/core/script/classic_script.h b/third_party/blink/renderer/core/script/classic_script.h
index 25e5e43..41f3ed1e 100644
--- a/third_party/blink/renderer/core/script/classic_script.h
+++ b/third_party/blink/renderer/core/script/classic_script.h
@@ -42,7 +42,7 @@
   mojom::ScriptType GetScriptType() const override {
     return mojom::ScriptType::kClassic;
   }
-  void RunScript(LocalFrame*, const SecurityOrigin*) const override;
+  void RunScript(LocalFrame*, const SecurityOrigin*) override;
   String InlineSourceTextForCSP() const override {
     return script_source_code_.Source().ToString();
   }
diff --git a/third_party/blink/renderer/core/script/dynamic_module_resolver_test.cc b/third_party/blink/renderer/core/script/dynamic_module_resolver_test.cc
index 9e3e01c..0f3c85f 100644
--- a/third_party/blink/renderer/core/script/dynamic_module_resolver_test.cc
+++ b/third_party/blink/renderer/core/script/dynamic_module_resolver_test.cc
@@ -87,7 +87,7 @@
     fetch_tree_was_called_ = true;
   }
 
-  ScriptValue ExecuteModule(const ModuleScript* module_script,
+  ScriptValue ExecuteModule(ModuleScript* module_script,
                             CaptureEvalErrorFlag capture_error) final {
     EXPECT_EQ(CaptureEvalErrorFlag::kCapture, capture_error);
 
diff --git a/third_party/blink/renderer/core/script/modulator.h b/third_party/blink/renderer/core/script/modulator.h
index 41f650a..2172280 100644
--- a/third_party/blink/renderer/core/script/modulator.h
+++ b/third_party/blink/renderer/core/script/modulator.h
@@ -198,8 +198,7 @@
   // and the caller should rethrow the returned exception. - When "rethrow
   // errors" is not to be set, use kReport. EvaluateModule() "report the error"
   // inside it (if any), and always returns null ScriptValue().
-  virtual ScriptValue ExecuteModule(const ModuleScript*,
-                                    CaptureEvalErrorFlag) = 0;
+  virtual ScriptValue ExecuteModule(ModuleScript*, CaptureEvalErrorFlag) = 0;
 
   virtual ModuleScriptFetcher* CreateModuleScriptFetcher(
       ModuleScriptCustomFetchType) = 0;
diff --git a/third_party/blink/renderer/core/script/modulator_impl_base.cc b/third_party/blink/renderer/core/script/modulator_impl_base.cc
index ad130f7..b49b7a9 100644
--- a/third_party/blink/renderer/core/script/modulator_impl_base.cc
+++ b/third_party/blink/renderer/core/script/modulator_impl_base.cc
@@ -261,7 +261,7 @@
 
 // <specdef href="https://html.spec.whatwg.org/C/#run-a-module-script">
 ScriptValue ModulatorImplBase::ExecuteModule(
-    const ModuleScript* module_script,
+    ModuleScript* module_script,
     CaptureEvalErrorFlag capture_error) {
   // <spec step="1">If rethrow errors is not given, let it be false.</spec>
 
diff --git a/third_party/blink/renderer/core/script/modulator_impl_base.h b/third_party/blink/renderer/core/script/modulator_impl_base.h
index fb4b174..8916dce 100644
--- a/third_party/blink/renderer/core/script/modulator_impl_base.h
+++ b/third_party/blink/renderer/core/script/modulator_impl_base.h
@@ -80,7 +80,7 @@
   ModuleImportMeta HostGetImportMetaProperties(ScriptModule) const override;
   ScriptValue InstantiateModule(ScriptModule) override;
   Vector<ModuleRequest> ModuleRequestsFromScriptModule(ScriptModule) override;
-  ScriptValue ExecuteModule(const ModuleScript*, CaptureEvalErrorFlag) override;
+  ScriptValue ExecuteModule(ModuleScript*, CaptureEvalErrorFlag) override;
 
   // Populates |reason| and returns true if the dynamic import is disallowed on
   // the associated execution context. In that case, a caller of this function
diff --git a/third_party/blink/renderer/core/script/module_script.cc b/third_party/blink/renderer/core/script/module_script.cc
index 0c2d2fd9..da03e43 100644
--- a/third_party/blink/renderer/core/script/module_script.cc
+++ b/third_party/blink/renderer/core/script/module_script.cc
@@ -226,7 +226,7 @@
   Script::Trace(visitor);
 }
 
-void ModuleScript::RunScript(LocalFrame* frame, const SecurityOrigin*) const {
+void ModuleScript::RunScript(LocalFrame* frame, const SecurityOrigin*) {
   DVLOG(1) << *this << "::RunScript()";
   settings_object_->ExecuteModule(this,
                                   Modulator::CaptureEvalErrorFlag::kReport);
diff --git a/third_party/blink/renderer/core/script/module_script.h b/third_party/blink/renderer/core/script/module_script.h
index 305035f..473de68aa 100644
--- a/third_party/blink/renderer/core/script/module_script.h
+++ b/third_party/blink/renderer/core/script/module_script.h
@@ -89,7 +89,7 @@
   mojom::ScriptType GetScriptType() const override {
     return mojom::ScriptType::kModule;
   }
-  void RunScript(LocalFrame*, const SecurityOrigin*) const override;
+  void RunScript(LocalFrame*, const SecurityOrigin*) override;
   String InlineSourceTextForCSP() const override;
 
   friend class ModulatorImplBase;
diff --git a/third_party/blink/renderer/core/script/script.h b/third_party/blink/renderer/core/script/script.h
index 70e4aff..0e5e9cf0 100644
--- a/third_party/blink/renderer/core/script/script.h
+++ b/third_party/blink/renderer/core/script/script.h
@@ -30,7 +30,7 @@
   // or
   // https://html.spec.whatwg.org/C/#run-a-module-script,
   // depending on the script type.
-  virtual void RunScript(LocalFrame*, const SecurityOrigin*) const = 0;
+  virtual void RunScript(LocalFrame*, const SecurityOrigin*) = 0;
 
   // For CSP check for inline scripts.
   virtual String InlineSourceTextForCSP() const = 0;
diff --git a/third_party/blink/renderer/core/script/script_runner.cc b/third_party/blink/renderer/core/script/script_runner.cc
index 95d55e5..9c50f4c 100644
--- a/third_party/blink/renderer/core/script/script_runner.cc
+++ b/third_party/blink/renderer/core/script/script_runner.cc
@@ -32,6 +32,7 @@
 #include "third_party/blink/renderer/core/script/script_loader.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
+#include "third_party/blink/renderer/platform/scheduler/public/cooperative_scheduling_manager.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
 
@@ -235,6 +236,12 @@
 }
 
 void ScriptRunner::ExecuteTask() {
+  // This method is triggered by ScriptRunner::PostTask, and runs directly from
+  // the scheduler. So, the call stack is safe to reenter.
+  scheduler::CooperativeSchedulingManager::WhitelistedStackScope
+      whitelisted_stack_scope(
+          scheduler::CooperativeSchedulingManager::Instance());
+
   if (is_suspended_)
     return;
 
diff --git a/third_party/blink/renderer/core/style/computed_style.cc b/third_party/blink/renderer/core/style/computed_style.cc
index 44f00c3..b93f8a6 100644
--- a/third_party/blink/renderer/core/style/computed_style.cc
+++ b/third_party/blink/renderer/core/style/computed_style.cc
@@ -2069,7 +2069,8 @@
     return 0;
   if (OutlineStyleIsAuto()) {
     return GraphicsContext::FocusRingOutsetExtent(
-        OutlineOffset(), std::ceil(GetOutlineStrokeWidthForFocusRing()));
+        OutlineOffset(), std::ceil(GetOutlineStrokeWidthForFocusRing()),
+        LayoutTheme::GetTheme().IsFocusRingOutset());
   }
   return ClampAdd(OutlineWidth(), OutlineOffset()).Max(0);
 }
@@ -2079,8 +2080,9 @@
   return OutlineWidth();
 #else
   // Draw an outline with thickness in proportion to the zoom level, but never
-  // less than 1 pixel so that it remains visible.
-  return std::max(EffectiveZoom(), 1.f);
+  // so narrow that it becomes invisible.
+  return std::max(EffectiveZoom(),
+                  LayoutTheme::GetTheme().MinimumStrokeWidthForFocusRing());
 #endif
 }
 
diff --git a/third_party/blink/renderer/core/style/computed_style_test.cc b/third_party/blink/renderer/core/style/computed_style_test.cc
index 32bf625..b3060da 100644
--- a/third_party/blink/renderer/core/style/computed_style_test.cc
+++ b/third_party/blink/renderer/core/style/computed_style_test.cc
@@ -77,6 +77,26 @@
 #endif
 }
 
+TEST(ComputedStyleTest, FocusRingCustomizedOutset) {
+  float old_minimum_stroke_width_for_focus_ring =
+      LayoutTheme::GetTheme().MinimumStrokeWidthForFocusRing();
+  bool old_is_focus_ring_outset = LayoutTheme::GetTheme().IsFocusRingOutset();
+  LayoutTheme::GetTheme().SetMinimumStrokeWidthForFocusRing(4.0);
+  LayoutTheme::GetTheme().SetIsFocusRingOutset(true);
+  scoped_refptr<ComputedStyle> style = ComputedStyle::Create();
+  style->SetOutlineStyle(EBorderStyle::kSolid);
+  style->SetOutlineStyleIsAuto(static_cast<bool>(OutlineIsAuto::kOn));
+  style->SetEffectiveZoom(4.75);
+#if defined(OS_MACOSX)
+  EXPECT_EQ(4, style->OutlineOutsetExtent());
+#else
+  EXPECT_EQ(5, style->OutlineOutsetExtent());
+#endif
+  LayoutTheme::GetTheme().SetMinimumStrokeWidthForFocusRing(
+      old_minimum_stroke_width_for_focus_ring);
+  LayoutTheme::GetTheme().SetIsFocusRingOutset(old_is_focus_ring_outset);
+}
+
 TEST(ComputedStyleTest, SVGStackingContext) {
   scoped_refptr<ComputedStyle> style = ComputedStyle::Create();
   style->UpdateIsStackingContext(false, false, true);
diff --git a/third_party/blink/renderer/core/style/style_reflection.h b/third_party/blink/renderer/core/style/style_reflection.h
index 68dcb1c..d2db3af6 100644
--- a/third_party/blink/renderer/core/style/style_reflection.h
+++ b/third_party/blink/renderer/core/style/style_reflection.h
@@ -55,7 +55,7 @@
  private:
   StyleReflection()
       : direction_(kReflectionBelow),
-        offset_(0, kFixed),
+        offset_(Length::Fixed(0)),
         mask_(NinePieceImage::MaskDefaults()) {}
 
   CSSReflectionDirection direction_;
diff --git a/third_party/blink/renderer/core/testing/dummy_modulator.cc b/third_party/blink/renderer/core/testing/dummy_modulator.cc
index dce4fe1..1a9b6eb 100644
--- a/third_party/blink/renderer/core/testing/dummy_modulator.cc
+++ b/third_party/blink/renderer/core/testing/dummy_modulator.cc
@@ -140,8 +140,7 @@
   return Vector<ModuleRequest>();
 }
 
-ScriptValue DummyModulator::ExecuteModule(const ModuleScript*,
-                                          CaptureEvalErrorFlag) {
+ScriptValue DummyModulator::ExecuteModule(ModuleScript*, CaptureEvalErrorFlag) {
   NOTREACHED();
   return ScriptValue();
 }
diff --git a/third_party/blink/renderer/core/testing/dummy_modulator.h b/third_party/blink/renderer/core/testing/dummy_modulator.h
index 2f4a0bd..63d30fe 100644
--- a/third_party/blink/renderer/core/testing/dummy_modulator.h
+++ b/third_party/blink/renderer/core/testing/dummy_modulator.h
@@ -62,7 +62,7 @@
   ModuleImportMeta HostGetImportMetaProperties(ScriptModule) const override;
   ScriptValue InstantiateModule(ScriptModule) override;
   Vector<ModuleRequest> ModuleRequestsFromScriptModule(ScriptModule) override;
-  ScriptValue ExecuteModule(const ModuleScript*, CaptureEvalErrorFlag) override;
+  ScriptValue ExecuteModule(ModuleScript*, CaptureEvalErrorFlag) override;
   ModuleScriptFetcher* CreateModuleScriptFetcher(
       ModuleScriptCustomFetchType) override;
 
diff --git a/third_party/blink/renderer/core/testing/internals.cc b/third_party/blink/renderer/core/testing/internals.cc
index b38b302..992d12c 100644
--- a/third_party/blink/renderer/core/testing/internals.cc
+++ b/third_party/blink/renderer/core/testing/internals.cc
@@ -33,8 +33,6 @@
 #include "base/optional.h"
 #include "cc/layers/picture_layer.h"
 #include "gpu/command_buffer/client/gles2_interface.h"
-#include "third_party/blink/public/platform/modules/remoteplayback/web_remote_playback_availability.h"
-#include "third_party/blink/public/platform/modules/remoteplayback/web_remote_playback_client.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/web_graphics_context_3d_provider.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_function.h"
@@ -100,6 +98,7 @@
 #include "third_party/blink/renderer/core/html/html_image_element.h"
 #include "third_party/blink/renderer/core/html/media/html_media_element.h"
 #include "third_party/blink/renderer/core/html/media/html_video_element.h"
+#include "third_party/blink/renderer/core/html/media/remote_playback_controller.h"
 #include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h"
 #include "third_party/blink/renderer/core/html_names.h"
 #include "third_party/blink/renderer/core/input/event_handler.h"
@@ -2500,20 +2499,18 @@
     HTMLMediaElement* media_element,
     bool available) {
   DCHECK(media_element);
-  DCHECK(media_element->remote_playback_client_);
-  media_element->remote_playback_client_->AvailabilityChanged(
-      available ? WebRemotePlaybackAvailability::kDeviceAvailable
-                : WebRemotePlaybackAvailability::kDeviceNotAvailable);
+
+  RemotePlaybackController::From(*media_element)
+      ->AvailabilityChangedForTesting(available);
 }
 
 void Internals::mediaPlayerPlayingRemotelyChanged(
     HTMLMediaElement* media_element,
     bool remote) {
   DCHECK(media_element);
-  if (remote)
-    media_element->ConnectedToRemoteDevice();
-  else
-    media_element->DisconnectedFromRemoteDevice();
+
+  RemotePlaybackController::From(*media_element)
+      ->StateChangedForTesting(remote);
 }
 
 void Internals::setPersistent(HTMLVideoElement* video_element,
diff --git a/third_party/blink/renderer/core/workers/dedicated_worker.cc b/third_party/blink/renderer/core/workers/dedicated_worker.cc
index be9ba2f..1c98d4a 100644
--- a/third_party/blink/renderer/core/workers/dedicated_worker.cc
+++ b/third_party/blink/renderer/core/workers/dedicated_worker.cc
@@ -154,7 +154,9 @@
       script_request_url_(script_request_url),
       options_(options),
       context_proxy_(
-          MakeGarbageCollected<DedicatedWorkerMessagingProxy>(context, this)) {
+          MakeGarbageCollected<DedicatedWorkerMessagingProxy>(context, this)),
+      v8_stack_trace_id_(ThreadDebugger::From(context->GetIsolate())
+                             ->StoreCurrentStackTrace("Worker Created")) {
   DCHECK(context->IsContextThread());
   DCHECK(script_request_url_.IsValid());
   DCHECK(context_proxy_);
@@ -213,10 +215,6 @@
 void DedicatedWorker::Start() {
   DCHECK(GetExecutionContext()->IsContextThread());
 
-  v8_inspector::V8StackTraceId stack_id =
-      ThreadDebugger::From(GetExecutionContext()->GetIsolate())
-          ->StoreCurrentStackTrace("Worker Created");
-
   if (auto* scope = DynamicTo<WorkerGlobalScope>(*GetExecutionContext()))
     scope->EnsureFetcher();
   if (blink::features::IsPlzDedicatedWorkerEnabled()) {
@@ -243,18 +241,9 @@
       options_->type() == "module") {
     // Specify empty source code here because scripts will be fetched on the
     // worker thread.
-    auto* outside_settings_object =
-        MakeGarbageCollected<FetchClientSettingsObjectSnapshot>(
-            GetExecutionContext()
-                ->Fetcher()
-                ->GetProperties()
-                .GetFetchClientSettingsObject());
-    context_proxy_->StartWorkerGlobalScope(
-        CreateGlobalScopeCreationParams(
-            script_request_url_, OffMainThreadWorkerScriptFetchOption::kEnabled,
-            network::mojom::ReferrerPolicy::kDefault),
-        options_, script_request_url_, *outside_settings_object, stack_id,
-        String() /* source_code */);
+    ContinueStart(
+        script_request_url_, OffMainThreadWorkerScriptFetchOption::kEnabled,
+        network::mojom::ReferrerPolicy::kDefault, String() /* source_code */);
     return;
   }
   if (options_->type() == "classic") {
@@ -269,8 +258,7 @@
         network::mojom::FetchCredentialsMode::kSameOrigin,
         GetExecutionContext()->GetSecurityContext().AddressSpace(),
         WTF::Bind(&DedicatedWorker::OnResponse, WrapPersistent(this)),
-        WTF::Bind(&DedicatedWorker::OnFinished, WrapPersistent(this),
-                  stack_id));
+        WTF::Bind(&DedicatedWorker::OnFinished, WrapPersistent(this)));
     return;
   }
   NOTREACHED() << "Invalid type: " << options_->type();
@@ -329,7 +317,11 @@
 
 void DedicatedWorker::OnScriptLoaded() {
   DCHECK(features::IsPlzDedicatedWorkerEnabled());
-  // TODO(nhiroki): Continue worker start.
+  // Specify empty source code here because scripts will be fetched on the
+  // worker thread.
+  ContinueStart(
+      script_request_url_, OffMainThreadWorkerScriptFetchOption::kEnabled,
+      network::mojom::ReferrerPolicy::kDefault, String() /* source_code */);
 }
 
 void DedicatedWorker::OnScriptLoadFailed() {
@@ -377,7 +369,7 @@
                                   classic_script_loader_->Identifier());
 }
 
-void DedicatedWorker::OnFinished(const v8_inspector::V8StackTraceId& stack_id) {
+void DedicatedWorker::OnFinished() {
   DCHECK(GetExecutionContext()->IsContextThread());
   if (classic_script_loader_->Canceled()) {
     // Do nothing.
@@ -398,18 +390,9 @@
     DCHECK(script_request_url_ == script_response_url ||
            SecurityOrigin::AreSameSchemeHostPort(script_request_url_,
                                                  script_response_url));
-    auto* outside_settings_object =
-        MakeGarbageCollected<FetchClientSettingsObjectSnapshot>(
-            GetExecutionContext()
-                ->Fetcher()
-                ->GetProperties()
-                .GetFetchClientSettingsObject());
-    context_proxy_->StartWorkerGlobalScope(
-        CreateGlobalScopeCreationParams(
-            script_response_url,
-            OffMainThreadWorkerScriptFetchOption::kDisabled, referrer_policy),
-        options_, script_response_url, *outside_settings_object, stack_id,
-        classic_script_loader_->SourceText());
+    ContinueStart(script_response_url,
+                  OffMainThreadWorkerScriptFetchOption::kDisabled,
+                  referrer_policy, classic_script_loader_->SourceText());
     probe::scriptImported(GetExecutionContext(),
                           classic_script_loader_->Identifier(),
                           classic_script_loader_->SourceText());
@@ -417,6 +400,24 @@
   classic_script_loader_ = nullptr;
 }
 
+void DedicatedWorker::ContinueStart(
+    const KURL& script_url,
+    OffMainThreadWorkerScriptFetchOption off_main_thread_fetch_option,
+    network::mojom::ReferrerPolicy referrer_policy,
+    const String& source_code) {
+  auto* outside_settings_object =
+      MakeGarbageCollected<FetchClientSettingsObjectSnapshot>(
+          GetExecutionContext()
+              ->Fetcher()
+              ->GetProperties()
+              .GetFetchClientSettingsObject());
+  context_proxy_->StartWorkerGlobalScope(
+      CreateGlobalScopeCreationParams(script_url, off_main_thread_fetch_option,
+                                      referrer_policy),
+      options_, script_url, *outside_settings_object, v8_stack_trace_id_,
+      source_code);
+}
+
 std::unique_ptr<GlobalScopeCreationParams>
 DedicatedWorker::CreateGlobalScopeCreationParams(
     const KURL& script_url,
@@ -440,6 +441,8 @@
                                       ? mojom::ScriptType::kClassic
                                       : mojom::ScriptType::kModule;
 
+  // TODO(nhiroki): Create WebWorkerFetchContext using |factory_client_| when
+  // PlzDedicatedWorker is enabled (https://crbug.com/906991).
   scoped_refptr<WebWorkerFetchContext> web_worker_fetch_context;
   if (auto* document = DynamicTo<Document>(GetExecutionContext())) {
     LocalFrame* frame = document->GetFrame();
diff --git a/third_party/blink/renderer/core/workers/dedicated_worker.h b/third_party/blink/renderer/core/workers/dedicated_worker.h
index 90e9745..e087647a 100644
--- a/third_party/blink/renderer/core/workers/dedicated_worker.h
+++ b/third_party/blink/renderer/core/workers/dedicated_worker.h
@@ -20,10 +20,7 @@
 #include "third_party/blink/renderer/platform/graphics/begin_frame_provider.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
-
-namespace v8_inspector {
-struct V8StackTraceId;
-}  // namespace v8_inspector
+#include "v8/include/v8-inspector.h"
 
 namespace blink {
 
@@ -42,6 +39,30 @@
 // called DedicatedWorker. This lives on the thread that created the worker (the
 // thread that called `new Worker()`), i.e., the main thread if a document
 // created the worker or a worker thread in the case of nested workers.
+//
+// We have been rearchitecting the worker startup sequence, and now there are
+// 3 paths as follows. The path is chosen based on runtime flags:
+//
+//  A) Legacy on-the-main-thread worker script loading (default)
+//  - DedicatedWorker::Start()
+//    - (Async script loading on the main thread)
+//      - DedicatedWorker::OnFinished()
+//        - DedicatedWorker::ContinueStart()
+//
+//  B) Off-the-main-thread worker script loading
+//     (kOffMainThreadDedicatedWorkerScriptFetch)
+//  - DedicatedWorker::Start()
+//    - DedicatedWorker::ContinueStart()
+//      - (Async script loading on the worker thread)
+//
+//  C) Off-the-main-thread worker script loading w/ PlzDedicatedWorker
+//     (kOffMainThreadDedicatedWorkerScriptFetch + kPlzDedicatedWorker +
+//      kNetworkService)
+//  - DedicatedWorker::Start()
+//    - (Start script loading in the browser)
+//      - DedicatedWorker::OnScriptLoaded()
+//        - DedicatedWorker::ContinueStart()
+//          - (Async script loading on the worker thread)
 class CORE_EXPORT DedicatedWorker final
     : public AbstractWorker,
       public ActiveScriptWrappable<DedicatedWorker>,
@@ -100,7 +121,10 @@
  private:
   // Starts the worker.
   void Start();
-
+  void ContinueStart(const KURL& script_url,
+                     OffMainThreadWorkerScriptFetchOption,
+                     network::mojom::ReferrerPolicy,
+                     const String& source_code);
   std::unique_ptr<GlobalScopeCreationParams> CreateGlobalScopeCreationParams(
       const KURL& script_url,
       OffMainThreadWorkerScriptFetchOption,
@@ -110,7 +134,7 @@
 
   // Callbacks for |classic_script_loader_|.
   void OnResponse();
-  void OnFinished(const v8_inspector::V8StackTraceId&);
+  void OnFinished();
 
   // Implements EventTarget (via AbstractWorker -> EventTargetWithInlineData).
   const AtomicString& InterfaceName() const final;
@@ -124,6 +148,9 @@
   // Used only when PlzDedicatedWorker is enabled.
   std::unique_ptr<WebDedicatedWorkerHostFactoryClient> factory_client_;
 
+  // Used for tracking cross-debugger calls.
+  const v8_inspector::V8StackTraceId v8_stack_trace_id_;
+
   service_manager::mojom::blink::InterfaceProviderPtrInfo interface_provider_;
 };
 
diff --git a/third_party/blink/renderer/core/workers/shared_worker_client_holder.cc b/third_party/blink/renderer/core/workers/shared_worker_client_holder.cc
index eab1ce52..08633f82 100644
--- a/third_party/blink/renderer/core/workers/shared_worker_client_holder.cc
+++ b/third_party/blink/renderer/core/workers/shared_worker_client_holder.cc
@@ -63,9 +63,11 @@
 }
 
 SharedWorkerClientHolder::SharedWorkerClientHolder(Document& document)
-    : ContextLifecycleObserver(&document) {
+    : ContextLifecycleObserver(&document),
+      task_runner_(document.GetTaskRunner(blink::TaskType::kDOMManipulation)) {
   DCHECK(IsMainThread());
-  document.GetInterfaceProvider()->GetInterface(mojo::MakeRequest(&connector_));
+  document.GetInterfaceProvider()->GetInterface(
+      mojo::MakeRequest(&connector_, task_runner_));
 }
 
 void SharedWorkerClientHolder::Connect(
@@ -97,7 +99,8 @@
 
   mojom::blink::SharedWorkerClientPtr client_ptr;
   client_set_.AddBinding(std::make_unique<SharedWorkerClient>(worker),
-                         mojo::MakeRequest(&client_ptr));
+                         mojo::MakeRequest(&client_ptr, task_runner_),
+                         task_runner_);
 
   connector_->Connect(
       std::move(info), std::move(client_ptr),
diff --git a/third_party/blink/renderer/core/workers/shared_worker_client_holder.h b/third_party/blink/renderer/core/workers/shared_worker_client_holder.h
index 02c177e..fd17d0b 100644
--- a/third_party/blink/renderer/core/workers/shared_worker_client_holder.h
+++ b/third_party/blink/renderer/core/workers/shared_worker_client_holder.h
@@ -87,6 +87,8 @@
   mojom::blink::SharedWorkerConnectorPtr connector_;
   mojo::StrongBindingSet<mojom::blink::SharedWorkerClient> client_set_;
 
+  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
   DISALLOW_COPY_AND_ASSIGN(SharedWorkerClientHolder);
 };
 
diff --git a/third_party/blink/renderer/modules/media_controls/media_controls_impl_test.cc b/third_party/blink/renderer/modules/media_controls/media_controls_impl_test.cc
index f6d7045..0f6800d 100644
--- a/third_party/blink/renderer/modules/media_controls/media_controls_impl_test.cc
+++ b/third_party/blink/renderer/modules/media_controls/media_controls_impl_test.cc
@@ -9,7 +9,6 @@
 
 #include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/public/platform/modules/remoteplayback/web_remote_playback_availability.h"
 #include "third_party/blink/public/platform/modules/remoteplayback/web_remote_playback_client.h"
 #include "third_party/blink/public/platform/web_mouse_event.h"
 #include "third_party/blink/public/platform/web_screen_info.h"
@@ -227,8 +226,8 @@
   }
 
   void SimulateRouteAvailable() {
-    media_controls_->MediaElement().RemoteRouteAvailabilityChanged(
-        WebRemotePlaybackAvailability::kDeviceAvailable);
+    RemotePlayback::From(media_controls_->MediaElement())
+        .AvailabilityChangedForTesting(/* screen_is_available */ true);
   }
 
   void EnsureSizing() {
diff --git a/third_party/blink/renderer/modules/remoteplayback/remote_playback.cc b/third_party/blink/renderer/modules/remoteplayback/remote_playback.cc
index 1c2521ca..5c8f34a 100644
--- a/third_party/blink/renderer/modules/remoteplayback/remote_playback.cc
+++ b/third_party/blink/renderer/modules/remoteplayback/remote_playback.cc
@@ -86,10 +86,8 @@
 RemotePlayback::RemotePlayback(HTMLMediaElement& element)
     : ContextLifecycleObserver(element.GetExecutionContext()),
       RemotePlaybackController(element),
-      state_(element.IsPlayingRemotely()
-                 ? WebRemotePlaybackState::kConnected
-                 : WebRemotePlaybackState::kDisconnected),
-      availability_(WebRemotePlaybackAvailability::kUnknown),
+      state_(WebRemotePlaybackState::kDisconnected),
+      availability_(mojom::ScreenAvailability::UNKNOWN),
       media_element_(&element),
       is_listening_(false),
       presentation_connection_binding_(this) {}
@@ -212,13 +210,13 @@
     return promise;
   }
 
-  if (availability_ == WebRemotePlaybackAvailability::kDeviceNotAvailable) {
+  if (availability_ == mojom::ScreenAvailability::UNAVAILABLE) {
     resolver->Reject(DOMException::Create(DOMExceptionCode::kNotFoundError,
                                           "No remote playback devices found."));
     return promise;
   }
 
-  if (availability_ == WebRemotePlaybackAvailability::kSourceNotCompatible) {
+  if (availability_ == mojom::ScreenAvailability::SOURCE_NOT_SUPPORTED) {
     resolver->Reject(DOMException::Create(
         DOMExceptionCode::kNotSupportedError,
         "The currentSrc is not compatible with remote playback"));
@@ -380,24 +378,6 @@
     observer->OnRemotePlaybackStateChanged(state_);
 }
 
-void RemotePlayback::AvailabilityChanged(
-    WebRemotePlaybackAvailability availability) {
-  if (availability_ == availability)
-    return;
-
-  bool old_availability = RemotePlaybackAvailable();
-  availability_ = availability;
-  bool new_availability = RemotePlaybackAvailable();
-  if (new_availability == old_availability)
-    return;
-
-  for (auto& callback : availability_callbacks_.Values())
-    callback->Run(this, new_availability);
-
-  for (auto observer : observers_)
-    observer->OnRemotePlaybackAvailabilityChanged(availability_);
-}
-
 void RemotePlayback::PromptCancelled() {
   if (!prompt_promise_resolver_)
     return;
@@ -442,6 +422,19 @@
   observers_.erase(observer);
 }
 
+void RemotePlayback::AvailabilityChangedForTesting(bool screen_is_available) {
+  // AvailabilityChanged() is only normally called when |is_listening_| is true.
+  is_listening_ = true;
+  AvailabilityChanged(screen_is_available
+                          ? mojom::blink::ScreenAvailability::AVAILABLE
+                          : mojom::blink::ScreenAvailability::UNAVAILABLE);
+}
+
+void RemotePlayback::StateChangedForTesting(bool is_connected) {
+  StateChanged(is_connected ? WebRemotePlaybackState::kConnected
+                            : WebRemotePlaybackState::kDisconnected);
+}
+
 bool RemotePlayback::RemotePlaybackAvailable() const {
   if (IsBackgroundAvailabilityMonitoringDisabled() &&
       RuntimeEnabledFeatures::RemotePlaybackBackendEnabled() &&
@@ -449,7 +442,7 @@
     return true;
   }
 
-  return availability_ == WebRemotePlaybackAvailability::kDeviceAvailable;
+  return availability_ == mojom::ScreenAvailability::AVAILABLE;
 }
 
 void RemotePlayback::RemotePlaybackDisabled() {
@@ -481,31 +474,20 @@
 void RemotePlayback::AvailabilityChanged(
     mojom::blink::ScreenAvailability availability) {
   DCHECK(is_listening_);
+  DCHECK_NE(availability, mojom::ScreenAvailability::UNKNOWN);
+  DCHECK_NE(availability, mojom::ScreenAvailability::DISABLED);
 
-  // TODO(avayvod): Use mojom::ScreenAvailability directly once
-  // WebRemotePlaybackAvailability is gone with the old pipeline.
-  WebRemotePlaybackAvailability remote_playback_availability =
-      WebRemotePlaybackAvailability::kUnknown;
-  switch (availability) {
-    case mojom::ScreenAvailability::UNKNOWN:
-    case mojom::ScreenAvailability::DISABLED:
-      NOTREACHED();
-      remote_playback_availability = WebRemotePlaybackAvailability::kUnknown;
-      break;
-    case mojom::ScreenAvailability::UNAVAILABLE:
-      remote_playback_availability =
-          WebRemotePlaybackAvailability::kDeviceNotAvailable;
-      break;
-    case mojom::ScreenAvailability::SOURCE_NOT_SUPPORTED:
-      remote_playback_availability =
-          WebRemotePlaybackAvailability::kSourceNotCompatible;
-      break;
-    case mojom::ScreenAvailability::AVAILABLE:
-      remote_playback_availability =
-          WebRemotePlaybackAvailability::kDeviceAvailable;
-      break;
-  }
-  AvailabilityChanged(remote_playback_availability);
+  if (availability_ == availability)
+    return;
+
+  bool old_availability = RemotePlaybackAvailable();
+  availability_ = availability;
+  bool new_availability = RemotePlaybackAvailable();
+  if (new_availability == old_availability)
+    return;
+
+  for (auto& callback : availability_callbacks_.Values())
+    callback->Run(this, new_availability);
 }
 
 const Vector<KURL>& RemotePlayback::Urls() const {
@@ -581,7 +563,7 @@
   if (!is_listening_)
     return;
 
-  availability_ = WebRemotePlaybackAvailability::kUnknown;
+  availability_ = mojom::ScreenAvailability::UNKNOWN;
   PresentationController* controller =
       PresentationController::FromContext(GetExecutionContext());
   if (!controller)
diff --git a/third_party/blink/renderer/modules/remoteplayback/remote_playback.h b/third_party/blink/renderer/modules/remoteplayback/remote_playback.h
index c483c4ee..6fbf600 100644
--- a/third_party/blink/renderer/modules/remoteplayback/remote_playback.h
+++ b/third_party/blink/renderer/modules/remoteplayback/remote_playback.h
@@ -7,7 +7,6 @@
 
 #include "mojo/public/cpp/bindings/binding.h"
 #include "third_party/blink/public/mojom/presentation/presentation.mojom-blink.h"
-#include "third_party/blink/public/platform/modules/remoteplayback/web_remote_playback_availability.h"
 #include "third_party/blink/public/platform/modules/remoteplayback/web_remote_playback_client.h"
 #include "third_party/blink/public/platform/modules/remoteplayback/web_remote_playback_state.h"
 #include "third_party/blink/public/platform/web_callbacks.h"
@@ -116,9 +115,6 @@
   void DidClose(mojom::blink::PresentationConnectionCloseReason) override;
 
   // WebRemotePlaybackClient implementation.
-  void StateChanged(WebRemotePlaybackState) override;
-  void AvailabilityChanged(WebRemotePlaybackAvailability) override;
-  void PromptCancelled() override;
   bool RemotePlaybackAvailable() const override;
   void SourceChanged(const WebURL&, bool is_source_supported) override;
   WebString GetPresentationId() override;
@@ -126,6 +122,8 @@
   // RemotePlaybackController implementation.
   void AddObserver(RemotePlaybackObserver*) override;
   void RemoveObserver(RemotePlaybackObserver*) override;
+  void AvailabilityChangedForTesting(bool screen_is_available) override;
+  void StateChangedForTesting(bool is_connected) override;
 
   // ScriptWrappable implementation.
   bool HasPendingActivity() const final;
@@ -133,6 +131,9 @@
   // ContextLifecycleObserver implementation.
   void ContextDestroyed(ExecutionContext*) override;
 
+  // Adjusts the internal state of |this| after a playback state change.
+  void StateChanged(WebRemotePlaybackState);
+
   DEFINE_ATTRIBUTE_EVENT_LISTENER(connecting, kConnecting)
   DEFINE_ATTRIBUTE_EVENT_LISTENER(connect, kConnect)
   DEFINE_ATTRIBUTE_EVENT_LISTENER(disconnect, kDisconnect)
@@ -144,6 +145,8 @@
   friend class RemotePlaybackTest;
   friend class MediaControlsImplTest;
 
+  void PromptCancelled();
+
   // Calls the specified availability callback with the current availability.
   // Need a void() method to post it as a task.
   void NotifyInitialAvailability(int callback_id);
@@ -161,7 +164,7 @@
   void CleanupConnections();
 
   WebRemotePlaybackState state_;
-  WebRemotePlaybackAvailability availability_;
+  mojom::blink::ScreenAvailability availability_;
   HeapHashMap<int, TraceWrapperMember<AvailabilityCallbackWrapper>>
       availability_callbacks_;
   Member<HTMLMediaElement> media_element_;
diff --git a/third_party/blink/renderer/platform/geometry/length.h b/third_party/blink/renderer/platform/geometry/length.h
index 56c9073..0ed49fed 100644
--- a/third_party/blink/renderer/platform/geometry/length.h
+++ b/third_party/blink/renderer/platform/geometry/length.h
@@ -185,34 +185,6 @@
 
   void SetQuirk(bool quirk) { quirk_ = quirk; }
 
-  void SetValue(LengthType t, int value) {
-    type_ = t;
-    int_value_ = value;
-    is_float_ = false;
-  }
-
-  void SetValue(int value) {
-    if (IsCalculated()) {
-      NOTREACHED();
-      return;
-    }
-    SetValue(kFixed, value);
-  }
-
-  void SetValue(LengthType t, float value) {
-    type_ = t;
-    float_value_ = value;
-    is_float_ = true;
-  }
-
-  void SetValue(LengthType t, LayoutUnit value) {
-    type_ = t;
-    float_value_ = value.ToFloat();
-    is_float_ = true;
-  }
-
-  void SetValue(float value) { *this = Length::Fixed(value); }
-
   bool IsMaxSizeNone() const { return GetType() == kMaxSizeNone; }
 
   // FIXME calc: https://bugs.webkit.org/show_bug.cgi?id=80357. A calculated
diff --git a/third_party/blink/renderer/platform/graphics/graphics_context.cc b/third_party/blink/renderer/platform/graphics/graphics_context.cc
index 30a2c99..8a3a86db 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_context.cc
+++ b/third_party/blink/renderer/platform/graphics/graphics_context.cc
@@ -391,21 +391,25 @@
 
 namespace {
 
-int AdjustedFocusRingOffset(int offset) {
+int AdjustedFocusRingOffset(int offset, int width, bool is_outset) {
 #if defined(OS_MACOSX)
   return offset + 2;
 #else
+  if (is_outset)
+    return offset + width - (width + 1) / 2;
   return 0;
 #endif
 }
 
 }  // namespace
 
-int GraphicsContext::FocusRingOutsetExtent(int offset, int width) {
+int GraphicsContext::FocusRingOutsetExtent(int offset,
+                                           int width,
+                                           bool is_outset) {
   // Unlike normal outlines (whole width is outside of the offset), focus
-  // rings are drawn with the center of the path aligned with the offset, so
+  // rings can be drawn with the center of the path aligned with the offset, so
   // only half of the width is outside of the offset.
-  return AdjustedFocusRingOffset(offset) + (width + 1) / 2;
+  return AdjustedFocusRingOffset(offset, width, is_outset) + (width + 1) / 2;
 }
 
 void GraphicsContext::DrawFocusRingPath(const SkPath& path,
@@ -436,7 +440,8 @@
 void GraphicsContext::DrawFocusRing(const Vector<IntRect>& rects,
                                     float width,
                                     int offset,
-                                    const Color& color) {
+                                    const Color& color,
+                                    bool is_outset) {
   if (ContextDisabled())
     return;
 
@@ -445,7 +450,7 @@
     return;
 
   SkRegion focus_ring_region;
-  offset = AdjustedFocusRingOffset(offset);
+  offset = AdjustedFocusRingOffset(offset, std::ceil(width), is_outset);
   for (unsigned i = 0; i < rect_count; i++) {
     SkIRect r = rects[i];
     if (r.isEmpty())
diff --git a/third_party/blink/renderer/platform/graphics/graphics_context.h b/third_party/blink/renderer/platform/graphics/graphics_context.h
index af3b81cb..c489eb3 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_context.h
+++ b/third_party/blink/renderer/platform/graphics/graphics_context.h
@@ -328,7 +328,8 @@
   void DrawFocusRing(const Vector<IntRect>&,
                      float width,
                      int offset,
-                     const Color&);
+                     const Color&,
+                     bool is_outset);
   void DrawFocusRing(const Path&, float width, int offset, const Color&);
 
   enum Edge {
@@ -384,7 +385,7 @@
                                           FloatPoint& p2,
                                           float stroke_width);
 
-  static int FocusRingOutsetExtent(int offset, int width);
+  static int FocusRingOutsetExtent(int offset, int width, bool is_outset);
 
   void SetInDrawingRecorder(bool);
   bool InDrawingRecorder() const { return in_drawing_recorder_; }
diff --git a/third_party/blink/renderer/platform/loader/BUILD.gn b/third_party/blink/renderer/platform/loader/BUILD.gn
index 92c75f14..4da8750 100644
--- a/third_party/blink/renderer/platform/loader/BUILD.gn
+++ b/third_party/blink/renderer/platform/loader/BUILD.gn
@@ -92,6 +92,8 @@
     "fetch/script_cached_metadata_handler.h",
     "fetch/script_fetch_options.cc",
     "fetch/script_fetch_options.h",
+    "fetch/shared_buffer_bytes_consumer.cc",
+    "fetch/shared_buffer_bytes_consumer.h",
     "fetch/source_keyed_cached_metadata_handler.cc",
     "fetch/source_keyed_cached_metadata_handler.h",
     "fetch/stale_revalidation_resource_client.cc",
@@ -147,6 +149,7 @@
     "fetch/resource_response_test.cc",
     "fetch/resource_test.cc",
     "fetch/response_body_loader_test.cc",
+    "fetch/shared_buffer_bytes_consumer_test.cc",
     "fetch/source_keyed_cached_metadata_handler_test.cc",
     "ftp_directory_listing_test.cc",
     "link_header_test.cc",
diff --git a/third_party/blink/renderer/platform/loader/fetch/client_hints_preferences.cc b/third_party/blink/renderer/platform/loader/fetch/client_hints_preferences.cc
index d9f21543..b05491fe 100644
--- a/third_party/blink/renderer/platform/loader/fetch/client_hints_preferences.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/client_hints_preferences.cc
@@ -48,6 +48,26 @@
       mojom::WebClientHintsType::kLang,
       enabled_hints.IsEnabled(mojom::WebClientHintsType::kLang) &&
           RuntimeEnabledFeatures::LangClientHintHeaderEnabled());
+
+  enabled_hints.SetIsEnabled(
+      mojom::WebClientHintsType::kUA,
+      enabled_hints.IsEnabled(mojom::WebClientHintsType::kUA) &&
+          RuntimeEnabledFeatures::UserAgentClientHintEnabled());
+
+  enabled_hints.SetIsEnabled(
+      mojom::WebClientHintsType::kUAArch,
+      enabled_hints.IsEnabled(mojom::WebClientHintsType::kUAArch) &&
+          RuntimeEnabledFeatures::UserAgentClientHintEnabled());
+
+  enabled_hints.SetIsEnabled(
+      mojom::WebClientHintsType::kUAPlatform,
+      enabled_hints.IsEnabled(mojom::WebClientHintsType::kUAPlatform) &&
+          RuntimeEnabledFeatures::UserAgentClientHintEnabled());
+
+  enabled_hints.SetIsEnabled(
+      mojom::WebClientHintsType::kUAModel,
+      enabled_hints.IsEnabled(mojom::WebClientHintsType::kUAModel) &&
+          RuntimeEnabledFeatures::UserAgentClientHintEnabled());
 }
 
 }  // namespace
diff --git a/third_party/blink/renderer/platform/loader/fetch/client_hints_preferences_test.cc b/third_party/blink/renderer/platform/loader/fetch/client_hints_preferences_test.cc
index dea2544..fcf6551 100644
--- a/third_party/blink/renderer/platform/loader/fetch/client_hints_preferences_test.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/client_hints_preferences_test.cc
@@ -25,22 +25,39 @@
     bool expectation_downlink;
     bool expectation_ect;
     bool expectation_lang;
+    bool expectation_ua;
+    bool expectation_ua_arch;
+    bool expectation_ua_platform;
+    bool expectation_ua_model;
   } cases[] = {
       {"width, dpr, viewportWidth", true, true, false, false, false, false,
-       false},
+       false, false, false, false, false},
       {"WiDtH, dPr, viewport-width, rtt, downlink, ect, lang", true, true, true,
-       true, true, true, true},
+       true, true, true, true, false, false, false, false},
       {"WiDtH, dPr, viewport-width, rtt, downlink, effective-connection-type",
-       true, true, true, true, true, false, false},
+       true, true, true, true, true, false, false, false, false, false, false},
       {"WIDTH, DPR, VIWEPROT-Width", true, true, false, false, false, false,
-       false},
+       false, false, false, false, false},
       {"VIewporT-Width, wutwut, width", true, false, true, false, false, false,
-       false},
-      {"dprw", false, false, false, false, false, false, false},
-      {"DPRW", false, false, false, false, false, false, false},
+       false, false, false, false, false},
+      {"dprw", false, false, false, false, false, false, false, false, false,
+       false, false},
+      {"DPRW", false, false, false, false, false, false, false, false, false,
+       false, false},
+      {"ua", false, false, false, false, false, false, false, true, false,
+       false, false},
+      {"arch", false, false, false, false, false, false, false, false, true,
+       false, false},
+      {"platform", false, false, false, false, false, false, false, false,
+       false, true, false},
+      {"model", false, false, false, false, false, false, false, false, false,
+       false, true},
+      {"ua, arch, platform, model", false, false, false, false, false, false,
+       false, true, true, true, true},
   };
 
   for (const auto& test_case : cases) {
+    SCOPED_TRACE(testing::Message() << test_case.header_value);
     ClientHintsPreferences preferences;
     const KURL kurl(String::FromUTF8("https://www.google.com/"));
     preferences.UpdateFromAcceptClientHintsHeader(test_case.header_value, kurl,
@@ -61,6 +78,14 @@
               preferences.ShouldSend(mojom::WebClientHintsType::kEct));
     EXPECT_EQ(test_case.expectation_lang,
               preferences.ShouldSend(mojom::WebClientHintsType::kLang));
+    EXPECT_EQ(test_case.expectation_ua,
+              preferences.ShouldSend(mojom::WebClientHintsType::kUA));
+    EXPECT_EQ(test_case.expectation_ua_arch,
+              preferences.ShouldSend(mojom::WebClientHintsType::kUAArch));
+    EXPECT_EQ(test_case.expectation_ua_platform,
+              preferences.ShouldSend(mojom::WebClientHintsType::kUAPlatform));
+    EXPECT_EQ(test_case.expectation_ua_model,
+              preferences.ShouldSend(mojom::WebClientHintsType::kUAModel));
 
     // Calling UpdateFromAcceptClientHintsHeader with empty header should have
     // no impact on client hint preferences.
@@ -105,6 +130,10 @@
   EXPECT_TRUE(preferences.ShouldSend(mojom::WebClientHintsType::kDownlink));
   EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kEct));
   EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kLang));
+  EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kUA));
+  EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kUAArch));
+  EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kUAPlatform));
+  EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kUAModel));
 
   // Calling UpdateFromAcceptClientHintsHeader with empty header should have
   // no impact on client hint preferences.
@@ -116,6 +145,10 @@
   EXPECT_TRUE(preferences.ShouldSend(mojom::WebClientHintsType::kDownlink));
   EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kEct));
   EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kLang));
+  EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kUA));
+  EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kUAArch));
+  EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kUAPlatform));
+  EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kUAModel));
 
   // Calling UpdateFromAcceptClientHintsHeader with an invalid header should
   // have no impact on client hint preferences.
@@ -126,6 +159,10 @@
   EXPECT_TRUE(preferences.ShouldSend(mojom::WebClientHintsType::kRtt));
   EXPECT_TRUE(preferences.ShouldSend(mojom::WebClientHintsType::kDownlink));
   EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kLang));
+  EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kUA));
+  EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kUAArch));
+  EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kUAPlatform));
+  EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kUAModel));
 
   // Calling UpdateFromAcceptClientHintsHeader with "width" header should
   // have no impact on already enabled client hint preferences.
@@ -137,6 +174,10 @@
   EXPECT_TRUE(preferences.ShouldSend(mojom::WebClientHintsType::kDownlink));
   EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kEct));
   EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kLang));
+  EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kUA));
+  EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kUAArch));
+  EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kUAPlatform));
+  EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kUAModel));
 
   preferences.UpdateFromAcceptClientHintsLifetimeHeader("1000", kurl, nullptr);
   EXPECT_EQ(base::TimeDelta::FromSeconds(1000),
@@ -170,21 +211,27 @@
     bool expect_downlink;
     bool expect_ect;
     bool expect_lang;
+    bool expect_ua;
+    bool expect_ua_arch;
+    bool expect_ua_platform;
+    bool expect_ua_model;
   } test_cases[] = {
       {"width, dpr, viewportWidth, lang", "", 0, false, true, true, false,
-       false, false, false, true},
+       false, false, false, true, false, false, false, false},
       {"width, dpr, viewportWidth", "-1000", 0, false, true, true, false, false,
-       false, false, false},
+       false, false, false, false, false, false, false},
       {"width, dpr, viewportWidth", "1000s", 0, false, true, true, false, false,
-       false, false, false},
+       false, false, false, false, false, false, false},
       {"width, dpr, viewportWidth", "1000.5", 0, false, true, true, false,
-       false, false, false, false},
+       false, false, false, false, false, false, false, false},
       {"width, dpr, rtt, downlink, ect", "1000", 1000, false, true, true, false,
-       true, true, true, false},
+       true, true, true, false, false, false, false, false},
       {"device-memory", "-1000", 0, true, false, false, false, false, false,
-       false, false},
+       false, false, false, false, false, false},
       {"dpr rtt", "1000", 1000, false, false, false, false, false, false, false,
-       false},
+       false, false, false, false, false},
+      {"ua, arch, platform, model", "1000", 1000, false, false, false, false,
+       false, false, false, false, true, true, true, true},
   };
 
   for (const auto& test : test_cases) {
@@ -202,6 +249,11 @@
     EXPECT_FALSE(enabled_types.IsEnabled(mojom::WebClientHintsType::kDownlink));
     EXPECT_FALSE(enabled_types.IsEnabled(mojom::WebClientHintsType::kEct));
     EXPECT_FALSE(enabled_types.IsEnabled(mojom::WebClientHintsType::kLang));
+    EXPECT_FALSE(enabled_types.IsEnabled(mojom::WebClientHintsType::kUA));
+    EXPECT_FALSE(enabled_types.IsEnabled(mojom::WebClientHintsType::kUAArch));
+    EXPECT_FALSE(
+        enabled_types.IsEnabled(mojom::WebClientHintsType::kUAPlatform));
+    EXPECT_FALSE(enabled_types.IsEnabled(mojom::WebClientHintsType::kUAModel));
     TimeDelta persist_duration = preferences.GetPersistDuration();
     EXPECT_EQ(base::TimeDelta(), persist_duration);
 
@@ -236,6 +288,14 @@
               enabled_types.IsEnabled(mojom::WebClientHintsType::kEct));
     EXPECT_EQ(test.expect_lang,
               enabled_types.IsEnabled(mojom::WebClientHintsType::kLang));
+    EXPECT_EQ(test.expect_ua,
+              enabled_types.IsEnabled(mojom::WebClientHintsType::kUA));
+    EXPECT_EQ(test.expect_ua_arch,
+              enabled_types.IsEnabled(mojom::WebClientHintsType::kUAArch));
+    EXPECT_EQ(test.expect_ua_platform,
+              enabled_types.IsEnabled(mojom::WebClientHintsType::kUAPlatform));
+    EXPECT_EQ(test.expect_ua_model,
+              enabled_types.IsEnabled(mojom::WebClientHintsType::kUAModel));
   }
 }
 
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource.h b/third_party/blink/renderer/platform/loader/fetch/resource.h
index f210994..0476b9a 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource.h
+++ b/third_party/blink/renderer/platform/loader/fetch/resource.h
@@ -85,7 +85,8 @@
   kAudio,
   kVideo,
   kManifest,
-  kMock  // Only for testing
+  kMock,  // Only for testing
+  kLast = kMock
 };
 
 // A resource that is held in the cache. Classes who want to use this object
@@ -619,7 +620,7 @@
 #define DEFINE_RESOURCE_TYPE_CASTS(typeName)                          \
   DEFINE_TYPE_CASTS(typeName##Resource, Resource, resource,           \
                     resource->GetType() == ResourceType::k##typeName, \
-                    resource.GetType() == ResourceType::k##typeName);
+                    resource.GetType() == ResourceType::k##typeName)
 
 }  // namespace blink
 
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
index 5242534e..39a53a95 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
@@ -616,8 +616,11 @@
   ResourceResponse response;
   scoped_refptr<SharedBuffer> data;
   if (url.ProtocolIsData()) {
-    data = network_utils::ParseDataURLAndPopulateResponse(url, response);
-    if (!data)
+    int result;
+    std::tie(result, response, data) =
+        network_utils::ParseDataURLAndPopulateResponse(
+            url, true /* verify_mime_type */);
+    if (result != net::OK)
       return nullptr;
     // |response| is modified by parseDataURLAndPopulateResponse() and is
     // ready to be used.
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc b/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
index 19c0c1b3b..bf5014c 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
@@ -47,6 +47,7 @@
 #include "third_party/blink/public/platform/web_url_request.h"
 #include "third_party/blink/public/platform/web_url_response.h"
 #include "third_party/blink/renderer/platform/exported/wrapped_resource_request.h"
+#include "third_party/blink/renderer/platform/exported/wrapped_resource_response.h"
 #include "third_party/blink/renderer/platform/loader/cors/cors.h"
 #include "third_party/blink/renderer/platform/loader/cors/cors_error_string.h"
 #include "third_party/blink/renderer/platform/loader/fetch/bytes_consumer_for_data_consumer_handle.h"
@@ -57,11 +58,13 @@
 #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher_properties.h"
 #include "third_party/blink/renderer/platform/loader/fetch/response_body_loader.h"
+#include "third_party/blink/renderer/platform/loader/fetch/shared_buffer_bytes_consumer.h"
 #include "third_party/blink/renderer/platform/loader/mixed_content_autoupgrade_status.h"
 #include "third_party/blink/renderer/platform/network/http_names.h"
 #include "third_party/blink/renderer/platform/network/http_parsers.h"
 #include "third_party/blink/renderer/platform/network/mime/mime_type_registry.h"
 #include "third_party/blink/renderer/platform/network/network_instrumentation.h"
+#include "third_party/blink/renderer/platform/network/network_utils.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
 #include "third_party/blink/renderer/platform/shared_buffer.h"
@@ -104,6 +107,39 @@
   builder.Record(recorder);
 }
 
+bool CanHandleDataURLRequestLocally(const ResourceRequest& request) {
+  if (!request.Url().ProtocolIsData())
+    return false;
+
+  // The fast paths for data URL, Start() and HandleDataURL(), don't support
+  // the DownloadToBlob option.
+  if (request.DownloadToBlob())
+    return false;
+
+  // Data url requests from object tags may need to be intercepted as streams
+  // and so need to be sent to the browser.
+  if (request.GetRequestContext() == mojom::RequestContextType::OBJECT)
+    return false;
+
+  // Optimize for the case where we can handle a data URL locally.  We must
+  // skip this for data URLs targeted at frames since those could trigger a
+  // download.
+  //
+  // NOTE: We special case MIME types we can render both for performance
+  // reasons as well as to support unit tests.
+  if (request.GetFrameType() !=
+          network::mojom::RequestContextFrameType::kTopLevel &&
+      request.GetFrameType() !=
+          network::mojom::RequestContextFrameType::kNested) {
+    return true;
+  }
+
+  if (network_utils::IsDataURLMimeTypeSupported(request.Url()))
+    return true;
+
+  return false;
+}
+
 }  // namespace
 
 // CodeCacheRequest handles the requests to fetch data from code cache.
@@ -485,24 +521,14 @@
     ResourceRequest cache_aware_request(request);
     cache_aware_request.SetCacheMode(
         mojom::FetchCacheMode::kUnspecifiedOnlyIfCachedStrict);
-    loader_->LoadAsynchronously(WrappedResourceRequest(cache_aware_request),
-                                this);
-    if (code_cache_request_) {
-      // Sets defers loading and initiates a fetch from code cache.
-      code_cache_request_->FetchFromCodeCache(loader_.get(), this);
-    }
+    RequestAsynchronously(cache_aware_request);
     return;
   }
 
   if (resource_->Options().synchronous_policy == kRequestSynchronously) {
     RequestSynchronously(request);
   } else {
-    loader_->LoadAsynchronously(WrappedResourceRequest(request), this);
-
-    if (code_cache_request_) {
-      // Sets defers loading and initiates a fetch from code cache.
-      code_cache_request_->FetchFromCodeCache(loader_.get(), this);
-    }
+    RequestAsynchronously(request);
   }
 }
 
@@ -523,6 +549,7 @@
 
 void ResourceLoader::SetDefersLoading(bool defers) {
   DCHECK(loader_);
+  defers_ = defers;
   // If CodeCacheRequest handles this, then no need to handle here.
   if (code_cache_request_ && code_cache_request_->SetDefersLoading(defers))
     return;
@@ -536,6 +563,15 @@
     }
   }
 
+  if (defers_handling_data_url_) {
+    if (!defers_) {
+      defers_handling_data_url_ = false;
+      GetLoadingTaskRunner()->PostTask(
+          FROM_HERE,
+          WTF::Bind(&ResourceLoader::HandleDataUrl, WrapWeakPersistent(this)));
+    }
+  }
+
   loader_->SetDefersLoading(defers);
   if (defers) {
     resource_->VirtualTimePauser().UnpauseVirtualTime();
@@ -573,6 +609,10 @@
       ResourceError::CancelledError(resource_->LastResourceRequest().Url()));
 }
 
+bool ResourceLoader::IsLoading() const {
+  return !!loader_;
+}
+
 void ResourceLoader::CancelForRedirectAccessCheckError(
     const KURL& new_url,
     ResourceRequestBlockedReason blocked_reason) {
@@ -822,22 +862,27 @@
     const WebURLResponse& web_url_response,
     std::unique_ptr<WebDataConsumerHandle> handle) {
   DCHECK(!web_url_response.IsNull());
+  DidReceiveResponseInternal(web_url_response.ToResourceResponse(),
+                             std::move(handle));
+}
 
+void ResourceLoader::DidReceiveResponseInternal(
+    const ResourceResponse& response,
+    std::unique_ptr<WebDataConsumerHandle> handle) {
   const ResourceRequest& request = resource_->GetResourceRequest();
 
   if (request.IsAutomaticUpgrade()) {
     auto recorder =
         ukm::MojoUkmRecorder::Create(Platform::Current()->GetConnector());
     LogMixedAutoupgradeMetrics(MixedContentAutoupgradeStatus::kResponseReceived,
-                               web_url_response.HttpStatusCode(),
+                               response.HttpStatusCode(),
                                request.GetUkmSourceId(), recorder.get());
   }
 
   if (fetcher_->GetProperties().IsDetached()) {
     // If the fetch context is already detached, we don't need further signals,
     // so let's cancel the request.
-    HandleError(
-        ResourceError::CancelledError(web_url_response.CurrentRequestUrl()));
+    HandleError(ResourceError::CancelledError(response.CurrentRequestUrl()));
     return;
   }
 
@@ -852,8 +897,6 @@
 
   const ResourceLoaderOptions& options = resource_->Options();
 
-  const ResourceResponse& response = web_url_response.ToResourceResponse();
-
   should_use_isolated_code_cache_ =
       ShouldUseIsolatedCodeCache(request_context, response);
 
@@ -1159,13 +1202,32 @@
   int64_t encoded_data_length = WebURLLoaderClient::kUnknownEncodedDataLength;
   int64_t encoded_body_length = 0;
   WebBlobInfo downloaded_blob;
-  loader_->LoadSynchronously(request_in, this, response_out, error_out,
-                             data_out, encoded_data_length, encoded_body_length,
-                             downloaded_blob);
 
+  if (CanHandleDataURLRequestLocally(request)) {
+    ResourceResponse response;
+    scoped_refptr<SharedBuffer> data;
+    int result;
+    // It doesn't have to verify mime type again since it's allowed to handle
+    // the data url with invalid mime type in some cases.
+    // CanHandleDataURLRequestLocally() has already checked if the data url can
+    // be handled here.
+    std::tie(result, response, data) =
+        network_utils::ParseDataURLAndPopulateResponse(
+            resource_->Url(), false /* verify_mime_type */);
+    if (result != net::OK) {
+      error_out = WebURLError(result, resource_->Url());
+    } else {
+      response_out = WrappedResourceResponse(response);
+      data_out = WebData(std::move(data));
+    }
+  } else {
+    loader_->LoadSynchronously(request_in, this, response_out, error_out,
+                               data_out, encoded_data_length,
+                               encoded_body_length, downloaded_blob);
+  }
   // A message dispatched while synchronously fetching the resource
   // can bring about the cancellation of this load.
-  if (!loader_)
+  if (!IsLoading())
     return;
   int64_t decoded_body_length = data_out.size();
   if (error_out) {
@@ -1174,7 +1236,7 @@
     return;
   }
   DidReceiveResponse(response_out);
-  if (!loader_)
+  if (!IsLoading())
     return;
   DCHECK_GE(response_out.ToResourceResponse().EncodedBodyLength(), 0);
 
@@ -1201,6 +1263,24 @@
                    std::vector<network::cors::PreflightTimingInfo>());
 }
 
+void ResourceLoader::RequestAsynchronously(const ResourceRequest& request) {
+  DCHECK(loader_);
+  if (CanHandleDataURLRequestLocally(request)) {
+    DCHECK(!code_cache_request_);
+    // Handle DataURL in another task instead of using |loader_|.
+    GetLoadingTaskRunner()->PostTask(
+        FROM_HERE,
+        WTF::Bind(&ResourceLoader::HandleDataUrl, WrapWeakPersistent(this)));
+    return;
+  }
+
+  loader_->LoadAsynchronously(WrappedResourceRequest(request), this);
+  if (code_cache_request_) {
+    // Sets defers loading and initiates a fetch from code cache.
+    code_cache_request_->FetchFromCodeCache(loader_.get(), this);
+  }
+}
+
 void ResourceLoader::Dispose() {
   loader_ = nullptr;
   progress_binding_.Close();
@@ -1313,6 +1393,49 @@
   return base::nullopt;
 }
 
+void ResourceLoader::HandleDataUrl() {
+  if (!IsLoading())
+    return;
+  if (defers_) {
+    defers_handling_data_url_ = true;
+    return;
+  }
+
+  // Extract a ResourceResponse from the data url.
+  ResourceResponse response;
+  scoped_refptr<SharedBuffer> data;
+  int result;
+  // We doesn't have to verify mime type again since it's allowed to handle the
+  // data url with invalid mime type in some cases.
+  // CanHandleDataURLRequestLocally() has already checked if the data url can be
+  // handled here.
+  std::tie(result, response, data) =
+      network_utils::ParseDataURLAndPopulateResponse(
+          resource_->Url(), false /* verify_mime_type */);
+  if (result != net::OK) {
+    HandleError(ResourceError(result, resource_->Url(), base::nullopt));
+    return;
+  }
+  DCHECK(data);
+  const size_t data_size = data->size();
+
+  DidReceiveResponseInternal(response, nullptr);
+  if (!IsLoading())
+    return;
+
+  auto* bytes_consumer =
+      MakeGarbageCollected<SharedBufferBytesConsumer>(std::move(data));
+  DidStartLoadingResponseBodyInternal(*bytes_consumer);
+  if (!IsLoading())
+    return;
+
+  // DidFinishLoading() may deferred until the response body loader reaches to
+  // end.
+  DidFinishLoading(base::TimeTicks::Now(), data_size, data_size, data_size,
+                   false /* should_report_corb_blocking */,
+                   {} /* cors_preflight_timing_info */);
+}
+
 bool ResourceLoader::ShouldCheckCorsInResourceLoader() const {
   return !RuntimeEnabledFeatures::OutOfBlinkCorsEnabled() &&
          resource_->Options().cors_handling_by_resource_fetcher ==
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_loader.h b/third_party/blink/renderer/platform/loader/fetch/resource_loader.h
index 4250e4e6..498766f0 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_loader.h
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_loader.h
@@ -186,11 +186,19 @@
 
   FetchContext& Context() const;
 
+  // Returns true during resource load is happening. Methods as
+  // a WebURLLoaderClient should not be invoked if this returns false.
+  bool IsLoading() const;
+
   void CancelForRedirectAccessCheckError(const KURL&,
                                          ResourceRequestBlockedReason);
   void RequestSynchronously(const ResourceRequest&);
+  void RequestAsynchronously(const ResourceRequest&);
   void Dispose();
 
+  void DidReceiveResponseInternal(const ResourceResponse&,
+                                  std::unique_ptr<WebDataConsumerHandle>);
+
   void CancelTimerFired(TimerBase*);
 
   void OnProgress(uint64_t delta) override;
@@ -202,6 +210,9 @@
       mojom::RequestContextType,
       const ResourceResponse&);
 
+  // Processes Data URL in ResourceLoader instead of using |loader_|.
+  void HandleDataUrl();
+
   bool ShouldCheckCorsInResourceLoader() const;
 
   std::unique_ptr<WebURLLoader> loader_;
@@ -239,6 +250,12 @@
   };
   base::Optional<DeferredFinishLoadingInfo> deferred_finish_loading_info_;
 
+  // True if loading is deferred.
+  bool defers_ = false;
+  // True if the next call of SetDefersLoading(false) needs to invoke
+  // HandleDataURL().
+  bool defers_handling_data_url_ = false;
+
   TaskRunnerTimer<ResourceLoader> cancel_timer_;
 };
 
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_loader_test.cc b/third_party/blink/renderer/platform/loader/fetch/resource_loader_test.cc
index 18704a9..2a00345 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_loader_test.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_loader_test.cc
@@ -4,7 +4,9 @@
 
 #include "third_party/blink/renderer/platform/loader/fetch/resource_loader.h"
 
+#include <string>
 #include <utility>
+
 #include "mojo/public/c/system/data_pipe.h"
 #include "services/network/public/mojom/fetch_api.mojom-shared.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -16,6 +18,7 @@
 #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_response.h"
+#include "third_party/blink/renderer/platform/loader/testing/bytes_consumer_test_reader.h"
 #include "third_party/blink/renderer/platform/loader/testing/mock_fetch_context.h"
 #include "third_party/blink/renderer/platform/loader/testing/test_resource_fetcher_properties.h"
 #include "third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.h"
@@ -263,6 +266,282 @@
   EXPECT_EQ(data, "hello");
 }
 
+TEST_F(ResourceLoaderTest, LoadDataURL_AsyncAndNonStream) {
+  auto* properties = MakeGarbageCollected<TestResourceFetcherProperties>();
+  FetchContext* context = MakeGarbageCollected<MockFetchContext>();
+  auto* fetcher = MakeGarbageCollected<ResourceFetcher>(
+      ResourceFetcherInit(*properties, context, CreateTaskRunner(),
+                          MakeGarbageCollected<NoopLoaderFactory>()));
+
+  // Fetch a data url.
+  KURL url("data:text/plain,Hello%20World!");
+  ResourceRequest request(url);
+  request.SetRequestContext(mojom::RequestContextType::FETCH);
+  FetchParameters params(request);
+  Resource* resource = RawResource::Fetch(params, fetcher, nullptr);
+  EXPECT_EQ(resource->GetStatus(), ResourceStatus::kPending);
+  static_cast<scheduler::FakeTaskRunner*>(fetcher->GetTaskRunner().get())
+      ->RunUntilIdle();
+
+  // The resource has a parsed body.
+  EXPECT_EQ(resource->GetStatus(), ResourceStatus::kCached);
+  scoped_refptr<const SharedBuffer> buffer = resource->ResourceBuffer();
+  String data;
+  for (const auto& span : *buffer) {
+    data.append(String(span.data(), span.size()));
+  }
+  EXPECT_EQ(data, "Hello World!");
+}
+
+// Helper class which stores a BytesConsumer passed by RawResource and reads the
+// bytes when ReadThroughBytesConsumer is called.
+class TestRawResourceClient final
+    : public GarbageCollectedFinalized<TestRawResourceClient>,
+      public RawResourceClient {
+  USING_GARBAGE_COLLECTED_MIXIN(TestRawResourceClient);
+
+ public:
+  TestRawResourceClient() = default;
+
+  // Implements RawResourceClient.
+  void ResponseBodyReceived(Resource* resource,
+                            BytesConsumer& bytes_consumer) override {
+    body_ = &bytes_consumer;
+  }
+  String DebugName() const override { return "TestRawResourceClient"; }
+
+  void Trace(Visitor* visitor) override {
+    visitor->Trace(body_);
+    RawResourceClient::Trace(visitor);
+  }
+
+  BytesConsumer* body() { return body_; }
+
+ private:
+  Member<BytesConsumer> body_;
+};
+
+TEST_F(ResourceLoaderTest, LoadDataURL_AsyncAndStream) {
+  auto* properties = MakeGarbageCollected<TestResourceFetcherProperties>();
+  FetchContext* context = MakeGarbageCollected<MockFetchContext>();
+  auto* fetcher = MakeGarbageCollected<ResourceFetcher>(
+      ResourceFetcherInit(*properties, context, CreateTaskRunner(),
+                          MakeGarbageCollected<NoopLoaderFactory>()));
+  scheduler::FakeTaskRunner* task_runner =
+      static_cast<scheduler::FakeTaskRunner*>(fetcher->GetTaskRunner().get());
+
+  // Fetch a data url as a stream on response.
+  KURL url("data:text/plain,Hello%20World!");
+  ResourceRequest request(url);
+  request.SetRequestContext(mojom::RequestContextType::FETCH);
+  request.SetUseStreamOnResponse(true);
+  FetchParameters params(request);
+  auto* raw_resource_client = MakeGarbageCollected<TestRawResourceClient>();
+  Resource* resource = RawResource::Fetch(params, fetcher, raw_resource_client);
+  EXPECT_EQ(resource->GetStatus(), ResourceStatus::kPending);
+  task_runner->RunUntilIdle();
+
+  // It's still pending because we don't read the body yet.
+  EXPECT_EQ(resource->GetStatus(), ResourceStatus::kPending);
+
+  // Read through the bytes consumer passed back from the ResourceLoader.
+  auto* test_reader = MakeGarbageCollected<BytesConsumerTestReader>(
+      raw_resource_client->body());
+  Vector<char> body;
+  BytesConsumer::Result result;
+  std::tie(result, body) = test_reader->Run(task_runner);
+  EXPECT_EQ(result, BytesConsumer::Result::kDone);
+  EXPECT_EQ(resource->GetStatus(), ResourceStatus::kCached);
+  EXPECT_EQ(std::string(body.data(), body.size()), "Hello World!");
+
+  // The body is not set to ResourceBuffer since the response body is requested
+  // as a stream.
+  scoped_refptr<const SharedBuffer> buffer = resource->ResourceBuffer();
+  EXPECT_FALSE(buffer);
+}
+
+TEST_F(ResourceLoaderTest, LoadDataURL_AsyncEmptyData) {
+  auto* properties = MakeGarbageCollected<TestResourceFetcherProperties>();
+  FetchContext* context = MakeGarbageCollected<MockFetchContext>();
+  auto* fetcher = MakeGarbageCollected<ResourceFetcher>(
+      ResourceFetcherInit(*properties, context, CreateTaskRunner(),
+                          MakeGarbageCollected<NoopLoaderFactory>()));
+
+  // Fetch an empty data url.
+  KURL url("data:text/html,");
+  ResourceRequest request(url);
+  request.SetRequestContext(mojom::RequestContextType::FETCH);
+  FetchParameters params(request);
+  Resource* resource = RawResource::Fetch(params, fetcher, nullptr);
+  EXPECT_EQ(resource->GetStatus(), ResourceStatus::kPending);
+  static_cast<scheduler::FakeTaskRunner*>(fetcher->GetTaskRunner().get())
+      ->RunUntilIdle();
+
+  // It successfully finishes, and no buffer is propagated.
+  EXPECT_EQ(resource->GetStatus(), ResourceStatus::kCached);
+  scoped_refptr<const SharedBuffer> buffer = resource->ResourceBuffer();
+  EXPECT_FALSE(buffer);
+}
+
+TEST_F(ResourceLoaderTest, LoadDataURL_Sync) {
+  auto* properties = MakeGarbageCollected<TestResourceFetcherProperties>();
+  FetchContext* context = MakeGarbageCollected<MockFetchContext>();
+  auto* fetcher = MakeGarbageCollected<ResourceFetcher>(
+      ResourceFetcherInit(*properties, context, CreateTaskRunner(),
+                          MakeGarbageCollected<NoopLoaderFactory>()));
+
+  // Fetch a data url synchronously.
+  KURL url("data:text/plain,Hello%20World!");
+  ResourceRequest request(url);
+  request.SetRequestContext(mojom::RequestContextType::FETCH);
+  FetchParameters params(request);
+  Resource* resource =
+      RawResource::FetchSynchronously(params, fetcher, nullptr);
+
+  // The resource has a parsed body.
+  EXPECT_EQ(resource->GetStatus(), ResourceStatus::kCached);
+  scoped_refptr<const SharedBuffer> buffer = resource->ResourceBuffer();
+  String data;
+  for (const auto& span : *buffer) {
+    data.append(String(span.data(), span.size()));
+  }
+  EXPECT_EQ(data, "Hello World!");
+}
+
+TEST_F(ResourceLoaderTest, LoadDataURL_SyncEmptyData) {
+  auto* properties = MakeGarbageCollected<TestResourceFetcherProperties>();
+  FetchContext* context = MakeGarbageCollected<MockFetchContext>();
+  auto* fetcher = MakeGarbageCollected<ResourceFetcher>(
+      ResourceFetcherInit(*properties, context, CreateTaskRunner(),
+                          MakeGarbageCollected<NoopLoaderFactory>()));
+
+  // Fetch an empty data url synchronously.
+  KURL url("data:text/html,");
+  ResourceRequest request(url);
+  request.SetRequestContext(mojom::RequestContextType::FETCH);
+  FetchParameters params(request);
+  Resource* resource =
+      RawResource::FetchSynchronously(params, fetcher, nullptr);
+
+  // It successfully finishes, and no buffer is propagated.
+  EXPECT_EQ(resource->GetStatus(), ResourceStatus::kCached);
+  scoped_refptr<const SharedBuffer> buffer = resource->ResourceBuffer();
+  EXPECT_FALSE(buffer);
+}
+
+TEST_F(ResourceLoaderTest, LoadDataURL_DefersAsyncAndNonStream) {
+  auto* properties = MakeGarbageCollected<TestResourceFetcherProperties>();
+  FetchContext* context = MakeGarbageCollected<MockFetchContext>();
+  auto* fetcher = MakeGarbageCollected<ResourceFetcher>(
+      ResourceFetcherInit(*properties, context, CreateTaskRunner(),
+                          MakeGarbageCollected<NoopLoaderFactory>()));
+  scheduler::FakeTaskRunner* task_runner =
+      static_cast<scheduler::FakeTaskRunner*>(fetcher->GetTaskRunner().get());
+
+  // Fetch a data url.
+  KURL url("data:text/plain,Hello%20World!");
+  ResourceRequest request(url);
+  request.SetRequestContext(mojom::RequestContextType::FETCH);
+  FetchParameters params(request);
+  Resource* resource = RawResource::Fetch(params, fetcher, nullptr);
+  EXPECT_EQ(resource->GetStatus(), ResourceStatus::kPending);
+
+  // The resource should still be pending since it's deferred.
+  fetcher->SetDefersLoading(true);
+  task_runner->RunUntilIdle();
+  EXPECT_EQ(resource->GetStatus(), ResourceStatus::kPending);
+
+  // The resource should still be pending since it's deferred again.
+  fetcher->SetDefersLoading(true);
+  task_runner->RunUntilIdle();
+  EXPECT_EQ(resource->GetStatus(), ResourceStatus::kPending);
+
+  // The resource should still be pending if it's unset and set in a single
+  // task.
+  fetcher->SetDefersLoading(false);
+  fetcher->SetDefersLoading(true);
+  task_runner->RunUntilIdle();
+  EXPECT_EQ(resource->GetStatus(), ResourceStatus::kPending);
+
+  // The resource has a parsed body.
+  fetcher->SetDefersLoading(false);
+  task_runner->RunUntilIdle();
+  EXPECT_EQ(resource->GetStatus(), ResourceStatus::kCached);
+  scoped_refptr<const SharedBuffer> buffer = resource->ResourceBuffer();
+  String data;
+  for (const auto& span : *buffer) {
+    data.append(String(span.data(), span.size()));
+  }
+  EXPECT_EQ(data, "Hello World!");
+}
+
+TEST_F(ResourceLoaderTest, LoadDataURL_DefersAsyncAndStream) {
+  auto* properties = MakeGarbageCollected<TestResourceFetcherProperties>();
+  FetchContext* context = MakeGarbageCollected<MockFetchContext>();
+  auto* fetcher = MakeGarbageCollected<ResourceFetcher>(
+      ResourceFetcherInit(*properties, context, CreateTaskRunner(),
+                          MakeGarbageCollected<NoopLoaderFactory>()));
+  scheduler::FakeTaskRunner* task_runner =
+      static_cast<scheduler::FakeTaskRunner*>(fetcher->GetTaskRunner().get());
+
+  // Fetch a data url as a stream on response.
+  KURL url("data:text/plain,Hello%20World!");
+  ResourceRequest request(url);
+  request.SetRequestContext(mojom::RequestContextType::FETCH);
+  request.SetUseStreamOnResponse(true);
+  FetchParameters params(request);
+  auto* raw_resource_client = MakeGarbageCollected<TestRawResourceClient>();
+  Resource* resource = RawResource::Fetch(params, fetcher, raw_resource_client);
+  EXPECT_EQ(resource->GetStatus(), ResourceStatus::kPending);
+  fetcher->SetDefersLoading(true);
+  task_runner->RunUntilIdle();
+
+  // It's still pending because the body should not provided yet.
+  EXPECT_EQ(resource->GetStatus(), ResourceStatus::kPending);
+  EXPECT_FALSE(raw_resource_client->body());
+
+  // The body should be provided since not deferring now, but it's still pending
+  // since we haven't read the body yet.
+  fetcher->SetDefersLoading(false);
+  task_runner->RunUntilIdle();
+  EXPECT_EQ(resource->GetStatus(), ResourceStatus::kPending);
+  EXPECT_TRUE(raw_resource_client->body());
+
+  // The resource should still be pending when it's set to deferred again. No
+  // body is provided when deferred.
+  fetcher->SetDefersLoading(true);
+  task_runner->RunUntilIdle();
+  EXPECT_EQ(resource->GetStatus(), ResourceStatus::kPending);
+  const char* buffer;
+  size_t available;
+  BytesConsumer::Result result =
+      raw_resource_client->body()->BeginRead(&buffer, &available);
+  EXPECT_EQ(BytesConsumer::Result::kShouldWait, result);
+
+  // The resource should still be pending if it's unset and set in a single
+  // task. No body is provided when deferred.
+  fetcher->SetDefersLoading(false);
+  fetcher->SetDefersLoading(true);
+  task_runner->RunUntilIdle();
+  EXPECT_EQ(resource->GetStatus(), ResourceStatus::kPending);
+  result = raw_resource_client->body()->BeginRead(&buffer, &available);
+  EXPECT_EQ(BytesConsumer::Result::kShouldWait, result);
+
+  // Read through the bytes consumer passed back from the ResourceLoader.
+  fetcher->SetDefersLoading(false);
+  task_runner->RunUntilIdle();
+  auto* test_reader = MakeGarbageCollected<BytesConsumerTestReader>(
+      raw_resource_client->body());
+  Vector<char> body;
+  std::tie(result, body) = test_reader->Run(task_runner);
+  EXPECT_EQ(resource->GetStatus(), ResourceStatus::kCached);
+  EXPECT_EQ(std::string(body.data(), body.size()), "Hello World!");
+
+  // The body is not set to ResourceBuffer since the response body is requested
+  // as a stream.
+  EXPECT_FALSE(resource->ResourceBuffer());
+}
+
 class ResourceLoaderIsolatedCodeCacheTest : public ResourceLoaderTest {
  protected:
   bool LoadAndCheckIsolatedCodeCache(ResourceResponse response) {
diff --git a/third_party/blink/renderer/platform/loader/fetch/shared_buffer_bytes_consumer.cc b/third_party/blink/renderer/platform/loader/fetch/shared_buffer_bytes_consumer.cc
new file mode 100644
index 0000000..f6a4434
--- /dev/null
+++ b/third_party/blink/renderer/platform/loader/fetch/shared_buffer_bytes_consumer.cc
@@ -0,0 +1,54 @@
+// Copyright 2019 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 "third_party/blink/renderer/platform/loader/fetch/shared_buffer_bytes_consumer.h"
+
+#include <utility>
+
+namespace blink {
+
+SharedBufferBytesConsumer::SharedBufferBytesConsumer(
+    scoped_refptr<const SharedBuffer> data)
+    : data_(std::move(data)), iterator_(data_->begin()) {}
+
+BytesConsumer::Result SharedBufferBytesConsumer::BeginRead(const char** buffer,
+                                                           size_t* available) {
+  *buffer = nullptr;
+  *available = 0;
+  if (iterator_ == data_->end())
+    return Result::kDone;
+  *buffer = iterator_->data() + bytes_read_in_chunk_;
+  *available = iterator_->size() - bytes_read_in_chunk_;
+  return Result::kOk;
+}
+
+BytesConsumer::Result SharedBufferBytesConsumer::EndRead(size_t read_size) {
+  DCHECK(iterator_ != data_->end());
+  DCHECK_LE(read_size + bytes_read_in_chunk_, iterator_->size());
+  bytes_read_in_chunk_ += read_size;
+  if (bytes_read_in_chunk_ == iterator_->size()) {
+    bytes_read_in_chunk_ = 0;
+    ++iterator_;
+  }
+  if (iterator_ == data_->end())
+    return Result::kDone;
+  return Result::kOk;
+}
+
+void SharedBufferBytesConsumer::Cancel() {
+  iterator_ = data_->end();
+  bytes_read_in_chunk_ = 0;
+}
+
+BytesConsumer::PublicState SharedBufferBytesConsumer::GetPublicState() const {
+  if (iterator_ == data_->end())
+    return PublicState::kClosed;
+  return PublicState::kReadableOrWaiting;
+}
+
+String SharedBufferBytesConsumer::DebugName() const {
+  return "SharedBufferBytesConsumer";
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/loader/fetch/shared_buffer_bytes_consumer.h b/third_party/blink/renderer/platform/loader/fetch/shared_buffer_bytes_consumer.h
new file mode 100644
index 0000000..5f848e1
--- /dev/null
+++ b/third_party/blink/renderer/platform/loader/fetch/shared_buffer_bytes_consumer.h
@@ -0,0 +1,42 @@
+// Copyright 2019 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 THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_SHARED_BUFFER_BYTES_CONSUMER_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_SHARED_BUFFER_BYTES_CONSUMER_H_
+
+#include "third_party/blink/renderer/platform/loader/fetch/bytes_consumer.h"
+#include "third_party/blink/renderer/platform/shared_buffer.h"
+
+namespace blink {
+
+// BytesConsumer to get data from a SharedBuffer.
+class PLATFORM_EXPORT SharedBufferBytesConsumer final : public BytesConsumer {
+ public:
+  // |data| should not be modified after it passed to SharedBufferBytesConsumer.
+  explicit SharedBufferBytesConsumer(scoped_refptr<const SharedBuffer> data);
+
+  // Implements BytesConsumer.
+  Result BeginRead(const char** buffer, size_t* available) override;
+  Result EndRead(size_t read_size) override;
+  void SetClient(Client* client) override {}
+  void ClearClient() override{};
+  void Cancel() override;
+  PublicState GetPublicState() const override;
+  Error GetError() const override {
+    NOTREACHED();
+    return Error();
+  };
+  String DebugName() const override;
+
+ private:
+  scoped_refptr<const SharedBuffer> data_;
+  SharedBuffer::Iterator iterator_;
+  size_t bytes_read_in_chunk_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(SharedBufferBytesConsumer);
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_SHARED_BUFFER_BYTES_CONSUMER_H_
diff --git a/third_party/blink/renderer/platform/loader/fetch/shared_buffer_bytes_consumer_test.cc b/third_party/blink/renderer/platform/loader/fetch/shared_buffer_bytes_consumer_test.cc
new file mode 100644
index 0000000..3b98c253
--- /dev/null
+++ b/third_party/blink/renderer/platform/loader/fetch/shared_buffer_bytes_consumer_test.cc
@@ -0,0 +1,68 @@
+// Copyright 2019 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 "third_party/blink/renderer/platform/loader/fetch/shared_buffer_bytes_consumer.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/platform/loader/testing/bytes_consumer_test_reader.h"
+#include "third_party/blink/renderer/platform/scheduler/test/fake_task_runner.h"
+#include "third_party/blink/renderer/platform/shared_buffer.h"
+
+namespace blink {
+
+using Result = BytesConsumer::Result;
+using PublicState = BytesConsumer::PublicState;
+
+TEST(SharedBufferBytesConsumerTest, Read) {
+  const std::vector<std::string> kData{"This is a expected data!",
+                                       "This is another data!"};
+  std::string flatten_expected_data;
+  auto shared_buffer = SharedBuffer::Create();
+  for (const auto& chunk : kData) {
+    shared_buffer->Append(chunk.data(), chunk.size());
+    flatten_expected_data += chunk;
+  }
+
+  auto* bytes_consumer =
+      MakeGarbageCollected<SharedBufferBytesConsumer>(std::move(shared_buffer));
+  EXPECT_EQ(PublicState::kReadableOrWaiting, bytes_consumer->GetPublicState());
+
+  auto task_runner = base::MakeRefCounted<scheduler::FakeTaskRunner>();
+  auto* test_reader =
+      MakeGarbageCollected<BytesConsumerTestReader>(bytes_consumer);
+  Vector<char> data_from_consumer;
+  Result result;
+  std::tie(result, data_from_consumer) = test_reader->Run(task_runner.get());
+  EXPECT_EQ(Result::kDone, result);
+  EXPECT_EQ(PublicState::kClosed, bytes_consumer->GetPublicState());
+  EXPECT_EQ(flatten_expected_data,
+            std::string(data_from_consumer.data(), data_from_consumer.size()));
+}
+
+TEST(SharedBufferBytesConsumerTest, Cancel) {
+  const std::vector<std::string> kData{"This is a expected data!",
+                                       "This is another data!"};
+  auto shared_buffer = SharedBuffer::Create();
+  for (const auto& chunk : kData) {
+    shared_buffer->Append(chunk.data(), chunk.size());
+  }
+
+  auto* bytes_consumer =
+      MakeGarbageCollected<SharedBufferBytesConsumer>(std::move(shared_buffer));
+  EXPECT_EQ(PublicState::kReadableOrWaiting, bytes_consumer->GetPublicState());
+
+  bytes_consumer->Cancel();
+  const char* buffer;
+  size_t available;
+  Result result = bytes_consumer->BeginRead(&buffer, &available);
+  EXPECT_EQ(0u, available);
+  EXPECT_EQ(Result::kDone, result);
+  EXPECT_EQ(PublicState::kClosed, bytes_consumer->GetPublicState());
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/network/network_utils.cc b/third_party/blink/renderer/platform/network/network_utils.cc
index a2172c0..3ecb398 100644
--- a/third_party/blink/renderer/platform/network/network_utils.cc
+++ b/third_party/blink/renderer/platform/network/network_utils.cc
@@ -65,9 +65,8 @@
   return String(domain.data(), domain.length());
 }
 
-scoped_refptr<SharedBuffer> ParseDataURLAndPopulateResponse(
-    const KURL& url,
-    ResourceResponse& response) {
+std::tuple<int, ResourceResponse, scoped_refptr<SharedBuffer>>
+ParseDataURLAndPopulateResponse(const KURL& url, bool verify_mime_type) {
   // The following code contains duplication of GetInfoFromDataURL() and
   // WebURLLoaderImpl::PopulateURLResponse() in
   // content/child/web_url_loader_impl.cc. Merge them once content/child is
@@ -81,18 +80,18 @@
   int result = net::URLRequestDataJob::BuildResponse(
       GURL(url), &utf8_mime_type, &utf8_charset, &data_string, headers.get());
   if (result != net::OK)
-    return nullptr;
+    return std::make_tuple(result, ResourceResponse(), nullptr);
 
-  if (!blink::IsSupportedMimeType(utf8_mime_type))
-    return nullptr;
+  if (verify_mime_type && !blink::IsSupportedMimeType(utf8_mime_type))
+    return std::make_tuple(net::ERR_FAILED, ResourceResponse(), nullptr);
 
-  scoped_refptr<SharedBuffer> data =
-      SharedBuffer::Create(data_string.data(), data_string.size());
+  auto buffer = SharedBuffer::Create(data_string.data(), data_string.size());
+  ResourceResponse response;
   response.SetHTTPStatusCode(200);
   response.SetHTTPStatusText("OK");
   response.SetCurrentRequestUrl(url);
   response.SetMimeType(WebString::FromUTF8(utf8_mime_type));
-  response.SetExpectedContentLength(data->size());
+  response.SetExpectedContentLength(buffer->size());
   response.SetTextEncodingName(WebString::FromUTF8(utf8_charset));
 
   size_t iter = 0;
@@ -102,7 +101,7 @@
     response.AddHTTPHeaderField(WebString::FromLatin1(name),
                                 WebString::FromLatin1(value));
   }
-  return data;
+  return std::make_tuple(net::OK, std::move(response), std::move(buffer));
 }
 
 bool IsDataURLMimeTypeSupported(const KURL& url) {
diff --git a/third_party/blink/renderer/platform/network/network_utils.h b/third_party/blink/renderer/platform/network/network_utils.h
index f161f69b..9d02246 100644
--- a/third_party/blink/renderer/platform/network/network_utils.h
+++ b/third_party/blink/renderer/platform/network/network_utils.h
@@ -5,6 +5,8 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_NETWORK_NETWORK_UTILS_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_NETWORK_NETWORK_UTILS_H_
 
+#include <tuple>
+
 #include "third_party/blink/renderer/platform/platform_export.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
 
@@ -28,11 +30,14 @@
 PLATFORM_EXPORT String GetDomainAndRegistry(const String& host,
                                             PrivateRegistryFilter);
 
-// Returns the decoded data url as ResourceResponse and SharedBuffer
-// if url had a supported mimetype and parsing was successful.
-PLATFORM_EXPORT scoped_refptr<SharedBuffer> ParseDataURLAndPopulateResponse(
-    const KURL&,
-    ResourceResponse&);
+// Returns the decoded data url as ResourceResponse and SharedBuffer if parsing
+// was successful. The result is returned as net error code. It returns net::OK
+// if decoding succeeds, otherwise it failed.
+// When |verify_mime_type| is true, it returns net::ERR_FAILED if the mime type
+// is not supported (blink::IsSupportedMimeType() returns false) even if parsing
+// succeeds.
+PLATFORM_EXPORT std::tuple<int, ResourceResponse, scoped_refptr<SharedBuffer>>
+ParseDataURLAndPopulateResponse(const KURL&, bool verify_mime_type);
 
 // Returns true if the URL is a data URL and its MIME type is in the list of
 // supported/recognized MIME types.
diff --git a/third_party/blink/renderer/platform/scheduler/BUILD.gn b/third_party/blink/renderer/platform/scheduler/BUILD.gn
index f6c0510..9f9ed92 100644
--- a/third_party/blink/renderer/platform/scheduler/BUILD.gn
+++ b/third_party/blink/renderer/platform/scheduler/BUILD.gn
@@ -11,6 +11,7 @@
   sources = [
     "common/cancelable_closure_holder.cc",
     "common/cancelable_closure_holder.h",
+    "common/cooperative_scheduling_manager.cc",
     "common/features.cc",
     "common/features.h",
     "common/frame_or_worker_scheduler.cc",
@@ -101,6 +102,7 @@
     "main_thread/web_render_widget_scheduling_state.cc",
     "main_thread/web_scoped_virtual_time_pauser.cc",
     "public/aggregated_metric_reporter.h",
+    "public/cooperative_scheduling_manager.h",
     "public/frame_or_worker_scheduler.h",
     "public/frame_scheduler.h",
     "public/frame_status.h",
@@ -178,6 +180,7 @@
   testonly = true
 
   sources = [
+    "common/cooperative_scheduling_manager_unittest.cc",
     "common/idle_helper_unittest.cc",
     "common/idle_memory_reclaimer_unittest.cc",
     "common/metrics_helper_unittest.cc",
diff --git a/third_party/blink/renderer/platform/scheduler/common/cooperative_scheduling_manager.cc b/third_party/blink/renderer/platform/scheduler/common/cooperative_scheduling_manager.cc
new file mode 100644
index 0000000..fbb4850
--- /dev/null
+++ b/third_party/blink/renderer/platform/scheduler/common/cooperative_scheduling_manager.cc
@@ -0,0 +1,48 @@
+// Copyright 2019 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 "third_party/blink/renderer/platform/scheduler/public/cooperative_scheduling_manager.h"
+
+#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
+#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
+#include "third_party/blink/renderer/platform/wtf/thread_specific.h"
+
+namespace blink {
+namespace scheduler {
+
+// static
+CooperativeSchedulingManager* CooperativeSchedulingManager::Instance() {
+  DEFINE_THREAD_SAFE_STATIC_LOCAL(ThreadSpecific<CooperativeSchedulingManager>,
+                                  manager, ());
+  return &(*manager);
+}
+
+CooperativeSchedulingManager::WhitelistedStackScope::WhitelistedStackScope(
+    CooperativeSchedulingManager* manager) {
+  cooperative_scheduling_manager_ = manager;
+  cooperative_scheduling_manager_->EnterWhitelistedStackScope();
+}
+
+CooperativeSchedulingManager::WhitelistedStackScope::~WhitelistedStackScope() {
+  cooperative_scheduling_manager_->LeaveWhitelistedStackScope();
+}
+
+CooperativeSchedulingManager::CooperativeSchedulingManager() {}
+
+void CooperativeSchedulingManager::EnterWhitelistedStackScope() {
+  TRACE_EVENT_ASYNC_BEGIN0("renderer.scheduler",
+                           "PreemptionWhitelistedStackScope", this);
+
+  whitelisted_stack_scope_depth_++;
+}
+
+void CooperativeSchedulingManager::LeaveWhitelistedStackScope() {
+  TRACE_EVENT_ASYNC_END0("renderer.scheduler",
+                         "PreemptionWhitelistedStackScope", this);
+  whitelisted_stack_scope_depth_--;
+  DCHECK_GE(whitelisted_stack_scope_depth_, 0);
+}
+
+}  // namespace scheduler
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/scheduler/common/cooperative_scheduling_manager_unittest.cc b/third_party/blink/renderer/platform/scheduler/common/cooperative_scheduling_manager_unittest.cc
new file mode 100644
index 0000000..a4720007
--- /dev/null
+++ b/third_party/blink/renderer/platform/scheduler/common/cooperative_scheduling_manager_unittest.cc
@@ -0,0 +1,30 @@
+// Copyright 2019 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 "third_party/blink/renderer/platform/scheduler/public/cooperative_scheduling_manager.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace blink {
+namespace scheduler {
+
+TEST(CooperativeSchedulingManager, WhitelistedStackScope) {
+  std::unique_ptr<CooperativeSchedulingManager> manager =
+      std::make_unique<CooperativeSchedulingManager>();
+  {
+    EXPECT_FALSE(manager->InWhitelistedStackScope());
+    CooperativeSchedulingManager::WhitelistedStackScope scope(manager.get());
+    EXPECT_TRUE(manager->InWhitelistedStackScope());
+    {
+      CooperativeSchedulingManager::WhitelistedStackScope nested_scope(
+          manager.get());
+      EXPECT_TRUE(manager->InWhitelistedStackScope());
+    }
+    EXPECT_TRUE(manager->InWhitelistedStackScope());
+  }
+  EXPECT_FALSE(manager->InWhitelistedStackScope());
+}
+
+}  // namespace scheduler
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/scheduler/public/cooperative_scheduling_manager.h b/third_party/blink/renderer/platform/scheduler/public/cooperative_scheduling_manager.h
new file mode 100644
index 0000000..d1b5e33
--- /dev/null
+++ b/third_party/blink/renderer/platform/scheduler/public/cooperative_scheduling_manager.h
@@ -0,0 +1,56 @@
+// Copyright 2019 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 THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_PUBLIC_COOPERATIVE_SCHEDULING_MANAGER_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_PUBLIC_COOPERATIVE_SCHEDULING_MANAGER_H_
+
+#include "third_party/blink/renderer/platform/platform_export.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
+#include "third_party/blink/renderer/platform/wtf/time.h"
+
+namespace blink {
+namespace scheduler {
+
+// This class manages the states for cooperative scheduling and decides whether
+// or not to run a nested loop or not.
+class PLATFORM_EXPORT CooperativeSchedulingManager {
+  USING_FAST_MALLOC(CooperativeSchedulingManager);
+
+ public:
+  // This class is used to mark JS executions that have a C++ stack that has
+  // been whitelisted for reentry.
+  class PLATFORM_EXPORT WhitelistedStackScope {
+    STACK_ALLOCATED();
+
+   public:
+    WhitelistedStackScope(CooperativeSchedulingManager*);
+    ~WhitelistedStackScope();
+
+   private:
+    CooperativeSchedulingManager* cooperative_scheduling_manager_;
+  };
+
+  // Returns an shared instance for the current thread.
+  static CooperativeSchedulingManager* Instance();
+
+  CooperativeSchedulingManager();
+
+  // Returns true if the C++ stack has been whitelisted for reentry.
+  bool InWhitelistedStackScope() const {
+    return whitelisted_stack_scope_depth_ > 0;
+  }
+
+ private:
+  void EnterWhitelistedStackScope();
+  void LeaveWhitelistedStackScope();
+
+  int whitelisted_stack_scope_depth_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(CooperativeSchedulingManager);
+};
+
+}  // namespace scheduler
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_PUBLIC_COOPERATIVE_SCHEDULING_MANAGER_H_
diff --git a/third_party/blink/renderer/platform/testing/weburl_loader_mock_factory_impl.cc b/third_party/blink/renderer/platform/testing/weburl_loader_mock_factory_impl.cc
index 1241d56..0046775f 100644
--- a/third_party/blink/renderer/platform/testing/weburl_loader_mock_factory_impl.cc
+++ b/third_party/blink/renderer/platform/testing/weburl_loader_mock_factory_impl.cc
@@ -149,9 +149,13 @@
   KURL kurl = params->url;
   if (kurl.ProtocolIsData()) {
     ResourceResponse response;
-    scoped_refptr<SharedBuffer> buffer =
-        network_utils::ParseDataURLAndPopulateResponse(kurl, response);
+    scoped_refptr<SharedBuffer> buffer;
+    int result;
+    std::tie(result, response, buffer) =
+        network_utils::ParseDataURLAndPopulateResponse(
+            kurl, true /* verify_mime_type */);
     DCHECK(buffer);
+    DCHECK_EQ(net::OK, result);
     params->response = WrappedResourceResponse(response);
     auto body_loader = std::make_unique<StaticDataNavigationBodyLoader>();
     body_loader->Write(*buffer);
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json
index a02e291..26066b1 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json
@@ -182866,6 +182866,11 @@
      {}
     ]
    ],
+   "screen-capture/feature-policy.https-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "screen-capture/getdisplaymedia.https-expected.txt": [
     [
      {}
@@ -277067,6 +277072,12 @@
      {}
     ]
    ],
+   "screen-capture/feature-policy.https.html": [
+    [
+     "/screen-capture/feature-policy.https.html",
+     {}
+    ]
+   ],
    "screen-capture/getdisplaymedia.https.html": [
     [
      "/screen-capture/getdisplaymedia.https.html",
@@ -394363,11 +394374,11 @@
    "support"
   ],
   "domparsing/XMLSerializer-serializeToString-expected.txt": [
-   "fc008ec96a92108431f392e24fbefa986a83960d",
+   "74a15ed0a603a456e68c8b358a1e34cae590f988",
    "support"
   ],
   "domparsing/XMLSerializer-serializeToString.html": [
-   "5f7e2bb0d4b459bd20acb43aca19b848920ac8bb",
+   "81df61f5bc42880ee013287e90a3808bdccd536b",
    "testharness"
   ],
   "domparsing/createContextualFragment.html": [
@@ -446270,6 +446281,14 @@
    "a1ef42657ad76cac417ccd52d68b9eafda283478",
    "support"
   ],
+  "screen-capture/feature-policy.https-expected.txt": [
+   "388368b82938b61cfa60994b89dd79c876316000",
+   "support"
+  ],
+  "screen-capture/feature-policy.https.html": [
+   "56c9e80a1307484a26ab370298789030a6fa350e",
+   "testharness"
+  ],
   "screen-capture/getdisplaymedia.https-expected.txt": [
    "2209b8770be42f3bf4da8b471ec2bfc6f9e4314c",
    "support"
@@ -463279,7 +463298,7 @@
    "support"
   ],
   "webxr/idlharness.https.window-expected.txt": [
-   "588b212fa11411f9fe57b1a56e2247f2735a2164",
+   "cb0fa2ae3e42af5854e11ff2fa7b159d69ee29ce",
    "support"
   ],
   "webxr/idlharness.https.window.js": [
diff --git a/third_party/blink/web_tests/external/wpt/domparsing/XMLSerializer-serializeToString.html b/third_party/blink/web_tests/external/wpt/domparsing/XMLSerializer-serializeToString.html
index 0c3c0a9..81df61f 100644
--- a/third_party/blink/web_tests/external/wpt/domparsing/XMLSerializer-serializeToString.html
+++ b/third_party/blink/web_tests/external/wpt/domparsing/XMLSerializer-serializeToString.html
@@ -27,28 +27,22 @@
 }
 
 test(function() {
-  var serializer = new XMLSerializer();
   var root = createXmlDoc().documentElement;
-  var xmlString = serializer.serializeToString(root);
-  assert_equals(xmlString, '<root><child1>value1</child1></root>');
+  assert_equals(serialize(root), '<root><child1>value1</child1></root>');
 }, 'check XMLSerializer.serializeToString method could parsing xmldoc to string');
 
 test(function() {
-  var serializer = new XMLSerializer();
   var root = createXmlDoc().documentElement;
   var element = root.ownerDocument.createElementNS('urn:foo', 'another');
   var child1 = root.firstChild;
   root.replaceChild(element, child1);
   element.appendChild(child1);
-  var xmlString = serializer.serializeToString(root);
-  assert_equals(xmlString, '<root><another xmlns="urn:foo"><child1 xmlns="">value1</child1></another></root>');
+  assert_equals(serialize(root), '<root><another xmlns="urn:foo"><child1 xmlns="">value1</child1></another></root>');
 }, 'Check if the default namespace is correctly reset.');
 
 test(function() {
-  var input = '<root xmlns="urn:bar"><outer xmlns=""><inner>value1</inner></outer></root>';
-  var root = (new DOMParser()).parseFromString(input, 'text/xml').documentElement;
-  var xmlString = (new XMLSerializer()).serializeToString(root);
-  assert_equals(xmlString, '<root xmlns="urn:bar"><outer xmlns=""><inner>value1</inner></outer></root>');
+  var root = parse('<root xmlns="urn:bar"><outer xmlns=""><inner>value1</inner></outer></root>');
+  assert_equals(serialize(root), '<root xmlns="urn:bar"><outer xmlns=""><inner>value1</inner></outer></root>');
 }, 'Check if there is no redundant empty namespace declaration.');
 
 test(function() {
@@ -124,34 +118,29 @@
 }, 'Check if the prefix of an attribute is replaced with a generated one in a case where the prefix is already mapped to a different namespace URI.');
 
 test(function() {
-  var serializer = new XMLSerializer();
-  var parser = new DOMParser();
-  var root = parser.parseFromString('<root />', 'text/xml').documentElement;
+  var root = parse('<root />');
   root.setAttribute('attr', '\t');
-  assert_in_array(serializer.serializeToString(root), [
+  assert_in_array(serialize(root), [
     '<root attr="&#9;"/>', '<root attr="&#x9;"/>']);
   root.setAttribute('attr', '\n');
-  assert_in_array(serializer.serializeToString(root), [
+  assert_in_array(serialize(root), [
     '<root attr="&#xA;"/>', '<root attr="&#10;"/>']);
   root.setAttribute('attr', '\r');
-  assert_in_array(serializer.serializeToString(root), [
+  assert_in_array(serialize(root), [
     '<root attr="&#xD;"/>', '<root attr="&#13;"/>']);
 }, 'check XMLSerializer.serializeToString escapes attribute values for roundtripping');
 
 test(function() {
   const root = (new Document()).createElement('root');
   root.setAttributeNS('uri1', 'p:foobar', 'value1');
-  root.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:p', 'uri2');
-  const xmlString = (new XMLSerializer()).serializeToString(root);
-  assert_equals(xmlString, '<root xmlns:ns1="uri1" ns1:foobar="value1" xmlns:p="uri2"/>');
+  root.setAttributeNS(XMLNS_URI, 'xmlns:p', 'uri2');
+  assert_equals(serialize(root), '<root xmlns:ns1="uri1" ns1:foobar="value1" xmlns:p="uri2"/>');
 }, 'Check if attribute serialization takes into account of following xmlns:* attributes');
 
 test(function() {
-  const input = '<root xmlns:p="uri1"><child/></root>';
-  const root = (new DOMParser()).parseFromString(input, 'text/xml').documentElement;
+  const root = parse('<root xmlns:p="uri1"><child/></root>');
   root.firstChild.setAttributeNS('uri2', 'p:foobar', 'v');
-  const xmlString = (new XMLSerializer()).serializeToString(root);
-  assert_equals(xmlString, '<root xmlns:p="uri1"><child xmlns:ns1="uri2" ns1:foobar="v"/></root>');
+  assert_equals(serialize(root), '<root xmlns:p="uri1"><child xmlns:ns1="uri2" ns1:foobar="v"/></root>');
 }, 'Check if attribute serialization takes into account of the same prefix declared in an ancestor element');
 
 test(function() {
@@ -176,30 +165,26 @@
 
 test(function() {
   const root = (new Document()).createElement('root');
-  root.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:p', 'uri2');
+  root.setAttributeNS(XMLNS_URI, 'xmlns:p', 'uri2');
   const child = root.ownerDocument.createElementNS('uri1', 'p:child');
   root.appendChild(child);
   assert_equals(serialize(root), '<root xmlns:p="uri2"><p:child xmlns:p="uri1"/></root>');
 }, 'Check if start tag serialization applied the original prefix even if it is declared in an ancestor element.');
 
 test(function() {
-  const input = '<root><child1/><child2/></root>';
-  const root = (new DOMParser()).parseFromString(input, 'text/xml').documentElement;
+  const root = parse('<root><child1/><child2/></root>');
   root.firstChild.setAttributeNS('uri1', 'attr1', 'value1');
   root.firstChild.setAttributeNS('uri2', 'attr2', 'value2');
   root.lastChild.setAttributeNS('uri3', 'attr3', 'value3');
-  const xmlString = (new XMLSerializer()).serializeToString(root);
-  assert_equals(xmlString, '<root><child1 xmlns:ns1="uri1" ns1:attr1="value1" xmlns:ns2="uri2" ns2:attr2="value2"/><child2 xmlns:ns3="uri3" ns3:attr3="value3"/></root>');
+  assert_equals(serialize(root), '<root><child1 xmlns:ns1="uri1" ns1:attr1="value1" xmlns:ns2="uri2" ns2:attr2="value2"/><child2 xmlns:ns3="uri3" ns3:attr3="value3"/></root>');
 }, 'Check if generated prefixes match to "ns${index}".');
 
 test(function() {
-  const input = '<root xmlns:ns2="uri2"><child xmlns:ns1="uri1"/></root>';
-  const root = (new DOMParser()).parseFromString(input, 'text/xml').documentElement;
+  const root = parse('<root xmlns:ns2="uri2"><child xmlns:ns1="uri1"/></root>');
   root.firstChild.setAttributeNS('uri3', 'attr1', 'value1');
-  const xmlString = (new XMLSerializer()).serializeToString(root);
   // According to 'DOM Parsing and Serialization' draft as of 2018-12-11,
   // 'generate a prefix' result can conflict with an existing xmlns:ns* declaration.
-  assert_equals(xmlString, '<root xmlns:ns2="uri2"><child xmlns:ns1="uri1" xmlns:ns1="uri3" ns1:attr1="value1"/></root>');
+  assert_equals(serialize(root), '<root xmlns:ns2="uri2"><child xmlns:ns1="uri1" xmlns:ns1="uri3" ns1:attr1="value1"/></root>');
 }, 'Check if "ns1" is generated even if the element already has xmlns:ns1.');
 
 test(function() {
diff --git a/third_party/blink/web_tests/external/wpt/screen-capture/feature-policy.https-expected.txt b/third_party/blink/web_tests/external/wpt/screen-capture/feature-policy.https-expected.txt
new file mode 100644
index 0000000..388368b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/screen-capture/feature-policy.https-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+FAIL Default "display-capture" feature policy ["self"] allows the top-level document. promise_test: Unhandled rejection with value: object "TypeError: Failed to execute 'getDisplayMedia' on 'MediaDevices': Audio only requests are not supported"
+FAIL Default "display-capture" feature policy ["self"] allows same-origin iframes. assert_equals: expected "#OK" but got "#TypeError"
+FAIL Default "display-capture" feature policy ["self"] disallows cross-origin iframes. assert_equals: expected "#NotAllowedError" but got "#TypeError"
+FAIL Feature policy "display-capture" can be enabled in cross-origin iframes using "allow" attribute. assert_equals: expected "#OK" but got "#TypeError"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/screen-capture/feature-policy.https.html b/third_party/blink/web_tests/external/wpt/screen-capture/feature-policy.https.html
new file mode 100644
index 0000000..56c9e80a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/screen-capture/feature-policy.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/common/get-host-info.sub.js></script>
+  <script src=/feature-policy/resources/featurepolicy.js></script>
+  <script>
+  'use strict';
+
+  async function gDM({audio, video}) {
+    let stream;
+    try {
+      stream = await navigator.mediaDevices.getDisplayMedia({audio, video});
+      if (stream.getVideoTracks().length == 0) {
+        throw {name: `requested video track must be present with ` +
+                     `audio ${audio} and video ${video}, or fail`};
+      }
+    } finally {
+      if (stream) {
+        stream.getTracks().forEach(track => track.stop());
+      }
+    }
+  }
+
+  const cross_domain = get_host_info().HTTPS_REMOTE_ORIGIN;
+  run_all_fp_tests_allow_self(
+    cross_domain,
+    'display-capture',
+    'NotAllowedError',
+    async () => {
+      await gDM({video: true});
+      await gDM({audio: true, video: true});
+      await gDM({audio: true});
+    }
+  );
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/fetch-request-xhr.https-expected.txt b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/fetch-request-xhr.https-expected.txt
index 76e0835..3de90db 100644
--- a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/fetch-request-xhr.https-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/fetch-request-xhr.https-expected.txt
@@ -1,9 +1,9 @@
 This is a testharness.js-based test.
 PASS initialize global state
-FAIL event.request has the expected headers for same-origin GET. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for same-origin GET. lengths differ, expected 1 got 6"
-FAIL event.request has the expected headers for same-origin POST. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for same-origin POST. lengths differ, expected 2 got 8"
-FAIL event.request has the expected headers for cross-origin GET. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for cross-origin GET. lengths differ, expected 1 got 6"
-FAIL event.request has the expected headers for cross-origin POST. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for cross-origin POST. lengths differ, expected 2 got 8"
+FAIL event.request has the expected headers for same-origin GET. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for same-origin GET. lengths differ, expected 1 got 7"
+FAIL event.request has the expected headers for same-origin POST. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for same-origin POST. lengths differ, expected 2 got 9"
+FAIL event.request has the expected headers for cross-origin GET. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for cross-origin GET. lengths differ, expected 1 got 7"
+FAIL event.request has the expected headers for cross-origin POST. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for cross-origin POST. lengths differ, expected 2 got 9"
 PASS FetchEvent#request.body contains XHR request data (string)
 PASS FetchEvent#request.body contains XHR request data (blob)
 PASS FetchEvent#request.method is set to XHR method
diff --git a/third_party/blink/web_tests/platform/win/media/video-played-ranges-1-expected.txt b/third_party/blink/web_tests/platform/win/media/video-played-ranges-1-expected.txt
deleted file mode 100644
index e69de29..0000000
--- a/third_party/blink/web_tests/platform/win/media/video-played-ranges-1-expected.txt
+++ /dev/null
diff --git a/third_party/dav1d/BUILD.gn b/third_party/dav1d/BUILD.gn
index aedc658..c50c7b4 100644
--- a/third_party/dav1d/BUILD.gn
+++ b/third_party/dav1d/BUILD.gn
@@ -29,11 +29,35 @@
     needs_stack_alignment = false
     # The defaults are stack_alignment=4 for x86 and stack_alignment=16 for x64.
   } else {
+    # The compiler flags, as well as the stack alignment values, all mirror
+    # upstream's meson.build setup:
+    # https://chromium.googlesource.com/external/github.com/videolan/dav1d/+/master/meson.build
     needs_stack_alignment = true
     if (current_cpu == "x86") {
       stack_alignment = 16
+
+      if (!is_clang) {
+        # Values used by GCC.
+        preferred_stack_boundary = 4
+        incoming_stack_boundary = 2
+      }
     } else if (current_cpu == "x64") {
       stack_alignment = 32
+
+      if (!is_clang) {
+        # Values used by GCC.
+        preferred_stack_boundary = 5
+        incoming_stack_boundary = 4
+      }
+    }
+
+    if (is_clang) {
+      stackalign_flag = "-mstack-alignment=$stack_alignment"
+      stackrealign_flag = "-mstackrealign"
+    } else {
+      # Assume GCC for now.
+      stackalign_flag = "-mpreferred-stack-boundary=$preferred_stack_boundary"
+      stackrealign_flag = "-mincoming-stack-boundary=$incoming_stack_boundary"
     }
   }
 } else {
@@ -73,7 +97,7 @@
 if (!is_win) {
   dav1d_copts += [ "-std=c99" ]
   if (needs_stack_alignment) {
-    dav1d_copts += [ "-mstack-alignment=$stack_alignment" ]
+    dav1d_copts += [ stackalign_flag ]
   }
 }
 
@@ -123,7 +147,7 @@
   }
 
   if (needs_stack_alignment) {
-    cflags += [ "-mstackrealign" ]
+    cflags += [ stackrealign_flag ]
   }
 }
 
diff --git a/tools/clang/scripts/update.py b/tools/clang/scripts/update.py
index 242a590..26e12ad 100755
--- a/tools/clang/scripts/update.py
+++ b/tools/clang/scripts/update.py
@@ -370,6 +370,16 @@
     f.write('group: files\n')
 
 
+def SetMacXcodePath():
+  """Set DEVELOPER_DIR to the path to hermetic Xcode.app on Mac OS X."""
+  if sys.platform != 'darwin':
+    return
+
+  xcode_path = os.path.join(CHROMIUM_DIR, 'build', 'mac_files', 'Xcode.app')
+  if os.path.exists(xcode_path):
+    os.environ['DEVELOPER_DIR'] = xcode_path
+
+
 win_sdk_dir = None
 dia_dll = None
 def GetWinSDKDir():
@@ -511,6 +521,7 @@
 
   AddCMakeToPath(args)
   AddGnuWinToPath()
+  SetMacXcodePath()
 
   DeleteChromeToolsShim()
 
diff --git a/tools/determinism/deterministic_build_whitelist.pyl b/tools/determinism/deterministic_build_whitelist.pyl
index 385e58a2..65c6551d 100644
--- a/tools/determinism/deterministic_build_whitelist.pyl
+++ b/tools/determinism/deterministic_build_whitelist.pyl
@@ -33,6 +33,8 @@
     'ppapi_nacl_tests_pnacl_newlib_x32_nonsfi.nexe',
     'test_data/ppapi/tests/extensions/packaged_app/nonsfi/ppapi_tests_extensions_packaged_app_pnacl_newlib_x32_nonsfi.nexe',
 
+    # https://crbug.com/932387
+    'performance_browser_tests',
   ],
 
   'linux_component': [
@@ -193,5 +195,9 @@
     # reason.
     'nacl_irt_x86_32.nexe',
     'nacl_irt_x86_32.nexe.debug',
+
+    # https://crbug.com/932387
+    'performance_browser_tests.exe',
+    'performance_browser_tests.exe.pdb',
   ],
 }
diff --git a/tools/gn/README.md b/tools/gn/README.chromium.md
similarity index 100%
rename from tools/gn/README.md
rename to tools/gn/README.chromium.md
diff --git a/tools/gn/bootstrap/bootstrap.py b/tools/gn/bootstrap/bootstrap.py
index 49a33bb..f12a63b9 100755
--- a/tools/gn/bootstrap/bootstrap.py
+++ b/tools/gn/bootstrap/bootstrap.py
@@ -91,13 +91,14 @@
               os.environ.get('CXXFLAGS', '').split()),
       ]) + '\n')
     subprocess.check_call(['ninja', '-C', libcxx_dir])
+    shutil.copy2(os.path.join(gn_build_dir, 'libc++.gn.so'), out_dir)
 
     def append_to_env(var, vals):
       os.putenv(var, os.environ.get(var, '') + ' ' + ' '.join(vals))
 
     append_to_env('LDFLAGS', [
-        '-nodefaultlibs', 'libc++.so', '-lc', '-lm', '-Wl,-rpath="\$$ORIGIN/."',
-        '-Wl,-rpath-link=.'
+        '-nodefaultlibs', 'libc++.gn.so', '-lc', '-lm',
+        '-Wl,-rpath="\$$ORIGIN/."', '-Wl,-rpath-link=.'
     ])
     append_to_env('CXXFLAGS', [
         '-nostdinc++',
diff --git a/tools/gn/bootstrap/libc++.ninja b/tools/gn/bootstrap/libc++.ninja
index 8851010..a22d0de 100644
--- a/tools/gn/bootstrap/libc++.ninja
+++ b/tools/gn/bootstrap/libc++.ninja
@@ -68,4 +68,4 @@
 build $libcxxabi/stdlib_stdexcept.o: cxx_libcxxabi $buildtools/$libcxxabi/stdlib_stdexcept.cpp
 build $libcxxabi/stdlib_typeinfo.o: cxx_libcxxabi $buildtools/$libcxxabi/stdlib_typeinfo.cpp
 
-build ../libc++.so: link $libcxx/algorithm.o $libcxx/any.o $libcxx/bind.o $libcxx/chrono.o $libcxx/condition_variable.o $libcxx/debug.o $libcxx/exception.o $libcxx/functional.o $libcxx/future.o $libcxx/hash.o $libcxx/ios.o $libcxx/iostream.o $libcxx/locale.o $libcxx/memory.o $libcxx/mutex.o $libcxx/new.o $libcxx/optional.o $libcxx/random.o $libcxx/regex.o $libcxx/shared_mutex.o $libcxx/stdexcept.o $libcxx/string.o $libcxx/strstream.o $libcxx/system_error.o $libcxx/thread.o $libcxx/typeinfo.o $libcxx/utility.o $libcxx/valarray.o $libcxx/variant.o $libcxx/vector.o $libcxxabi/abort_message.o $libcxxabi/cxa_aux_runtime.o $libcxxabi/cxa_default_handlers.o $libcxxabi/cxa_demangle.o $libcxxabi/cxa_exception_storage.o $libcxxabi/cxa_guard.o $libcxxabi/cxa_handlers.o $libcxxabi/cxa_noexception.o $libcxxabi/cxa_unexpected.o $libcxxabi/cxa_vector.o $libcxxabi/cxa_virtual.o $libcxxabi/fallback_malloc.o $libcxxabi/private_typeinfo.o $libcxxabi/stdlib_exception.o $libcxxabi/stdlib_stdexcept.o $libcxxabi/stdlib_typeinfo.o
+build ../libc++.gn.so: link $libcxx/algorithm.o $libcxx/any.o $libcxx/bind.o $libcxx/chrono.o $libcxx/condition_variable.o $libcxx/debug.o $libcxx/exception.o $libcxx/functional.o $libcxx/future.o $libcxx/hash.o $libcxx/ios.o $libcxx/iostream.o $libcxx/locale.o $libcxx/memory.o $libcxx/mutex.o $libcxx/new.o $libcxx/optional.o $libcxx/random.o $libcxx/regex.o $libcxx/shared_mutex.o $libcxx/stdexcept.o $libcxx/string.o $libcxx/strstream.o $libcxx/system_error.o $libcxx/thread.o $libcxx/typeinfo.o $libcxx/utility.o $libcxx/valarray.o $libcxx/variant.o $libcxx/vector.o $libcxxabi/abort_message.o $libcxxabi/cxa_aux_runtime.o $libcxxabi/cxa_default_handlers.o $libcxxabi/cxa_demangle.o $libcxxabi/cxa_exception_storage.o $libcxxabi/cxa_guard.o $libcxxabi/cxa_handlers.o $libcxxabi/cxa_noexception.o $libcxxabi/cxa_unexpected.o $libcxxabi/cxa_vector.o $libcxxabi/cxa_virtual.o $libcxxabi/fallback_malloc.o $libcxxabi/private_typeinfo.o $libcxxabi/stdlib_exception.o $libcxxabi/stdlib_stdexcept.o $libcxxabi/stdlib_typeinfo.o
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 2776392..de314d1 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -460,9 +460,9 @@
   <int value="2" label="Maximized"/>
   <int value="3" label="Fullscreen"/>
   <int value="4" label="Snapped"/>
-  <int value="5" label="Docked"/>
-  <int value="6" label="Pinned"/>
-  <int value="7" label="TrustedPinned"/>
+  <int value="5" label="Pinned"/>
+  <int value="6" label="TrustedPinned"/>
+  <int value="7" label="Picture in picture"/>
 </enum>
 
 <enum name="ActivityTrackerAnalyzerCreationError">
@@ -3636,6 +3636,7 @@
   <int value="16" label="Deprecated Explore Sites refresh task"/>
   <int value="17" label="Explore Sites refresh task"/>
   <int value="18" label="Download auto-resumption task"/>
+  <int value="19" label="One shot Background Sync wake up task"/>
 </enum>
 
 <enum name="BackgroundTracingState">
@@ -9789,6 +9790,10 @@
   <int value="4" label="Checksum out of sync"/>
 </enum>
 
+<enum name="CryptohomeDeprecatedApiCalled">
+  <int value="0" label="InitializeCastKey"/>
+</enum>
+
 <enum name="CryptohomeDiskCleanupProgress">
   <int value="1" label="Ephemeral User Profiles cleaned">
     Ephemeral users were enabled. Removed all profiles except those currently
@@ -21577,6 +21582,10 @@
   <int value="2786" label="V8UserActivation_IsActive_AttributeGetter"/>
   <int value="2787" label="TextEncoderEncodeInto"/>
   <int value="2788" label="InvalidBasicCardMethodData"/>
+  <int value="2789" label="ClientHintsUA"/>
+  <int value="2790" label="ClientHintsUAArch"/>
+  <int value="2791" label="ClientHintsUAPlatform"/>
+  <int value="2792" label="ClientHintsUAModel"/>
 </enum>
 
 <enum name="FeaturePolicyFeature">
@@ -31478,6 +31487,7 @@
   <int value="-757946835"
       label="OmniboxUIExperimentShowSuggestionFavicons:enabled"/>
   <int value="-757379927" label="Canvas2DImageChromium:enabled"/>
+  <int value="-757282194" label="EnableFilesystemInIncognito:enabled"/>
   <int value="-750175757" label="ClientLoFi:enabled"/>
   <int value="-749048160" label="enable-panels"/>
   <int value="-747463111" label="ContentSuggestionsNotifications:disabled"/>
@@ -32145,6 +32155,7 @@
   <int value="446316019" label="enable-threaded-compositing"/>
   <int value="451196246" label="disable-impl-side-painting"/>
   <int value="452139294" label="VrShellExperimentalRendering:enabled"/>
+  <int value="452955571" label="EnableFilesystemInIncognito:disabled"/>
   <int value="453102772" label="OfflinePagesLoadSignalCollecting:disabled"/>
   <int value="455698038"
       label="disable-gesture-requirement-for-media-playback"/>
@@ -32307,6 +32318,7 @@
   <int value="720931007" label="WebAuthenticationBle:enabled"/>
   <int value="721225492" label="PolicyTool:enabled"/>
   <int value="723619383" label="TopSitesFromSiteEngagement:enabled"/>
+  <int value="724052572" label="EnableFilesystemInIncognito"/>
   <int value="724208771" label="TabsInCBD:enabled"/>
   <int value="725270017" label="ScrollAnchorSerialization:disabled"/>
   <int value="726764779" label="WebXRGamepadSupport:enabled"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 2dd4383..f3b4594 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -18935,6 +18935,17 @@
   </summary>
 </histogram>
 
+<histogram name="Cryptohome.DeprecatedApiCalled"
+    enum="CryptohomeDeprecatedApiCalled" expires_after="M80">
+  <owner>apronin@chromium.org</owner>
+  <owner>louiscollard@chromium.org</owner>
+  <owner>zuan@chromium.org</owner>
+  <summary>
+    Records when a deprecated API function in cryptohome is called, so we know
+    which exposed DBus API can be removed without side effect.
+  </summary>
+</histogram>
+
 <histogram name="Cryptohome.DircryptoMigrationEndStatus"
     enum="DircryptoMigrationEndStatus">
   <owner>dspaid@chromium.org</owner>
@@ -26373,6 +26384,9 @@
 
 <histogram name="Drive.FilesListRequestRunner.ApiErrorCode"
     enum="DriveApiErrorCode">
+  <obsolete>
+    Obsolete 02/2019 as DriveFS implementation obsoletes this metric.
+  </obsolete>
   <owner>mtomasz@chromium.org</owner>
   <summary>
     Error codes returned by the Drive API for files list requests executed via
@@ -26381,6 +26395,9 @@
 </histogram>
 
 <histogram name="Drive.FilesListRequestRunner.MaxResults">
+  <obsolete>
+    Obsolete 02/2019 as DriveFS implementation obsoletes this metric.
+  </obsolete>
   <owner>mtomasz@chromium.org</owner>
   <summary>
     Maximum number of results for each files list request using the Drive API.
diff --git a/tools/perf/core/cli_helpers.py b/tools/perf/core/cli_helpers.py
index 45b3f370..f23a984c 100644
--- a/tools/perf/core/cli_helpers.py
+++ b/tools/perf/core/cli_helpers.py
@@ -21,7 +21,7 @@
 }
 
 
-def colored(message, color):
+def Colored(message, color):
   """Wraps the message into ASCII color escape codes.
 
   Args:
@@ -34,39 +34,39 @@
   return '\033[%dm%s\033[0m' % (COLOR_ANSI_CODE_MAP[color], message)
 
 
-def info(message, **kwargs):
+def Info(message, **kwargs):
   print(message.format(**kwargs))
 
 
-def comment(message, **kwargs):
+def Comment(message, **kwargs):
   """Prints an import message to the user."""
-  print(colored(message.format(**kwargs), 'yellow'))
+  print(Colored(message.format(**kwargs), 'yellow'))
 
 
-def fatal(message, **kwargs):
+def Fatal(message, **kwargs):
   """Displays an error to the user and terminates the program."""
-  error(message, **kwargs)
+  Error(message, **kwargs)
   sys.exit(1)
 
 
-def error(message, **kwargs):
+def Error(message, **kwargs):
   """Displays an error to the user."""
-  print(colored(message.format(**kwargs), 'red'))
+  print(Colored(message.format(**kwargs), 'red'))
 
 
-def step(name):
+def Step(name):
   """Display a decorated message to the user.
 
   This is useful to separate major stages of the script. For simple messages,
   please use comment function above.
   """
   boundary = max(80, len(name))
-  print(colored('=' * boundary, 'green'))
-  print(colored(name, 'green'))
-  print(colored('=' * boundary, 'green'))
+  print(Colored('=' * boundary, 'green'))
+  print(Colored(name, 'green'))
+  print(Colored('=' * boundary, 'green'))
 
 
-def ask(question, answers=None, default=None):
+def Ask(question, answers=None, default=None):
   """Asks the user to answer a question with multiple choices.
 
   Users are able to press Return to access the default answer (if specified) and
@@ -121,7 +121,7 @@
     raise ValueError('invalid default answer: "%s"' % default)
 
   while True:
-    print(colored(question + prompt, 'cyan'), end=' ')
+    print(Colored(question + prompt, 'cyan'), end=' ')
     choice = raw_input().strip().lower()
     if default is not None and choice == '':
       return inputs[default]
@@ -129,11 +129,11 @@
       return inputs[choice]
     else:
       choices = sorted(['"%s"' % a for a in sorted(answers.keys())])
-      error('Please respond with %s or %s.' % (
+      Error('Please respond with %s or %s.' % (
         ', '.join(choices[:-1]), choices[-1]))
 
 
-def check_log(command, log_path, env=None):
+def CheckLog(command, log_path, env=None):
   """Executes a command and writes its stdout to a specified log file.
 
   On non-zero return value, also prints the content of the file to the screen
@@ -146,27 +146,30 @@
   """
   with open(log_path, 'w') as f:
     try:
-      cmd_str = ' '.join(command) if isinstance(command, list) else command
-      print(colored(cmd_str, 'blue'))
-      print(colored('Logging stdout & stderr to %s' % log_path, 'blue'))
+      cmd_str = (' '.join(pipes.quote(c) for c in command)
+                 if isinstance(command, list) else command)
+      print(Colored(cmd_str, 'blue'))
+      print(Colored('Logging stdout & stderr to %s' % log_path, 'blue'))
       subprocess.check_call(
           command, stdout=f, stderr=subprocess.STDOUT, shell=False, env=env)
     except subprocess.CalledProcessError:
-      error('=' * 80)
-      error('Received non-zero return code. Log content:')
-      error('=' * 80)
+      Error('=' * 80)
+      Error('Received non-zero return code. Log content:')
+      Error('=' * 80)
       subprocess.call(['cat', log_path])
-      error('=' * 80)
+      Error('=' * 80)
       raise
 
 
-def run(command, ok_fail=False, **kwargs):
+def Run(command, ok_fail=False, **kwargs):
   """Prints and runs the command. Allows to ignore non-zero exit code."""
   if not isinstance(command, list):
     raise ValueError('command must be a list')
-  print(colored(' '.join(pipes.quote(c) for c in command), 'blue'))
+  print(Colored(' '.join(pipes.quote(c) for c in command), 'blue'))
   try:
     return subprocess.check_call(command, **kwargs)
-  except subprocess.CalledProcessError:
+  except subprocess.CalledProcessError as cpe:
     if not ok_fail:
       raise
+    else:
+      return cpe.returncode
diff --git a/tools/perf/core/cli_helpers_unittest.py b/tools/perf/core/cli_helpers_unittest.py
index c103684..8be9f2d 100644
--- a/tools/perf/core/cli_helpers_unittest.py
+++ b/tools/perf/core/cli_helpers_unittest.py
@@ -13,34 +13,34 @@
 class CLIHelpersTest(unittest.TestCase):
   def testUnsupportedColor(self):
     with self.assertRaises(AssertionError):
-      cli_helpers.colored('message', 'pink')
+      cli_helpers.Colored('message', 'pink')
 
   @mock.patch('__builtin__.print')
   def testPrintsInfo(self, print_mock):
-    cli_helpers.info('foo {sval} {ival}', sval='s', ival=42)
+    cli_helpers.Info('foo {sval} {ival}', sval='s', ival=42)
     print_mock.assert_called_once_with('foo s 42')
 
   @mock.patch('__builtin__.print')
   def testPrintsComment(self, print_mock):
-    cli_helpers.comment('foo')
+    cli_helpers.Comment('foo')
     print_mock.assert_called_once_with('\033[93mfoo\033[0m')
 
   @mock.patch('__builtin__.print')
   @mock.patch('sys.exit')
   def testFatal(self, sys_exit_mock, print_mock):
-    cli_helpers.fatal('foo')
+    cli_helpers.Fatal('foo')
     print_mock.assert_called_once_with('\033[91mfoo\033[0m')
     sys_exit_mock.assert_called_once()
 
   @mock.patch('__builtin__.print')
   def testPrintsError(self, print_mock):
-    cli_helpers.error('foo')
+    cli_helpers.Error('foo')
     print_mock.assert_called_once_with('\033[91mfoo\033[0m')
 
   @mock.patch('__builtin__.print')
   def testPrintsStep(self, print_mock):
     long_step_name = 'foobar' * 15
-    cli_helpers.step(long_step_name)
+    cli_helpers.Step(long_step_name)
     self.assertListEqual(print_mock.call_args_list, [
         mock.call('\033[92m' + ('=' * 90) + '\033[0m'),
         mock.call('\033[92m' + long_step_name + '\033[0m'),
@@ -51,7 +51,7 @@
   @mock.patch('__builtin__.raw_input')
   def testAskAgainOnInvalidAnswer(self, raw_input_mock, print_mock):
     raw_input_mock.side_effect = ['foobar', 'y']
-    self.assertTrue(cli_helpers.ask('Ready?'))
+    self.assertTrue(cli_helpers.Ask('Ready?'))
     self.assertListEqual(print_mock.mock_calls, [
       mock.call('\033[96mReady? [no/YES] \033[0m', end=' '),
       mock.call('\033[91mPlease respond with "no" or "yes".\033[0m'),
@@ -63,7 +63,7 @@
   def testAskWithCustomAnswersAndDefault(self, raw_input_mock, print_mock):
     raw_input_mock.side_effect = ['']
     self.assertFalse(
-        cli_helpers.ask('Ready?', {'foo': True, 'bar': False}, default='bar'))
+        cli_helpers.Ask('Ready?', {'foo': True, 'bar': False}, default='bar'))
     print_mock.assert_called_once_with(
         '\033[96mReady? [BAR/foo] \033[0m', end=' ')
 
@@ -71,7 +71,7 @@
   @mock.patch('__builtin__.raw_input')
   def testAskNoDefaultCustomAnswersAsList(self, raw_input_mock, print_mock):
     raw_input_mock.side_effect = ['', 'FoO']
-    self.assertEqual(cli_helpers.ask('Ready?', ['foo', 'bar']), 'foo')
+    self.assertEqual(cli_helpers.Ask('Ready?', ['foo', 'bar']), 'foo')
     self.assertListEqual(print_mock.mock_calls, [
       mock.call('\033[96mReady? [foo/bar] \033[0m', end=' '),
       mock.call('\033[91mPlease respond with "bar" or "foo".\033[0m'),
@@ -80,7 +80,7 @@
 
   def testAskWithInvalidDefaultAnswer(self):
     with self.assertRaises(ValueError):
-      cli_helpers.ask('Ready?', ['foo', 'bar'], 'baz')
+      cli_helpers.Ask('Ready?', ['foo', 'bar'], 'baz')
 
   @mock.patch('__builtin__.print')
   @mock.patch('subprocess.check_call')
@@ -92,20 +92,23 @@
     open_mock.return_value.__enter__.return_value = file_mock
     dt_mock.now.return_value.strftime.return_value = '_2018_12_10_16_22_11'
 
-    cli_helpers.check_log(
-        ['command', 'arg1'], '/tmp/tmpXYZ.tmp', env={'foo': 'bar'})
+    cli_helpers.CheckLog(
+        ['command', 'arg with space'], '/tmp/tmpXYZ.tmp', env={'foo': 'bar'})
 
     check_call_mock.assert_called_once_with(
-        ['command', 'arg1'], stdout=file_mock, stderr=subprocess.STDOUT,
-        shell=False, env={'foo': 'bar'})
+        ['command', 'arg with space'],
+        stdout=file_mock,
+        stderr=subprocess.STDOUT,
+        shell=False,
+        env={'foo': 'bar'})
     open_mock.assert_called_once_with('/tmp/tmpXYZ.tmp', 'w')
     self.assertListEqual(print_mock.mock_calls, [
-      mock.call('\033[94mcommand arg1\033[0m'),
+      mock.call("\033[94mcommand 'arg with space'\033[0m"),
       mock.call('\033[94mLogging stdout & stderr to /tmp/tmpXYZ.tmp\033[0m'),
     ])
 
   @mock.patch('__builtin__.print')
-  @mock.patch('core.cli_helpers.error')
+  @mock.patch('core.cli_helpers.Error')
   @mock.patch('subprocess.check_call')
   @mock.patch('subprocess.call')
   @mock.patch('__builtin__.open')
@@ -115,7 +118,7 @@
     check_call_mock.side_effect = [subprocess.CalledProcessError(87, ['cmd'])]
 
     with self.assertRaises(subprocess.CalledProcessError):
-      cli_helpers.check_log(['cmd'], '/tmp/tmpXYZ.tmp')
+      cli_helpers.CheckLog(['cmd'], '/tmp/tmpXYZ.tmp')
 
     call_mock.assert_called_once_with(['cat', '/tmp/tmpXYZ.tmp'])
     self.assertListEqual(error_mock.mock_calls, [
@@ -130,7 +133,7 @@
   def testRun(self, check_call_mock, print_mock):
     check_call_mock.side_effect = [subprocess.CalledProcessError(87, ['cmd'])]
     with self.assertRaises(subprocess.CalledProcessError):
-      cli_helpers.run(['cmd', 'arg with space'], env={'a': 'b'})
+      cli_helpers.Run(['cmd', 'arg with space'], env={'a': 'b'})
     check_call_mock.assert_called_once_with(
         ['cmd', 'arg with space'], env={'a': 'b'})
     print_mock.assert_called_once_with('\033[94mcmd \'arg with space\'\033[0m')
@@ -140,11 +143,11 @@
   def testRunOkFail(self, check_call_mock, print_mock):
     del print_mock  # Unused.
     check_call_mock.side_effect = [subprocess.CalledProcessError(87, ['cmd'])]
-    cli_helpers.run(['cmd'], ok_fail=True)
+    cli_helpers.Run(['cmd'], ok_fail=True)
 
   def testRunWithNonListCommand(self):
     with self.assertRaises(ValueError):
-      cli_helpers.run('cmd with args')
+      cli_helpers.Run('cmd with args')
 
 
 if __name__ == "__main__":
diff --git a/tools/perf/update_wpr b/tools/perf/update_wpr
new file mode 100755
index 0000000..0e235e3
--- /dev/null
+++ b/tools/perf/update_wpr
@@ -0,0 +1,11 @@
+#!/usr/bin/env python
+# Copyright 2018 the V8 project authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import sys
+
+from wpr import update_wpr
+
+if __name__ == '__main__':
+  sys.exit(update_wpr.Main(sys.argv[1:]))
diff --git a/tools/perf/wpr/__init__.py b/tools/perf/wpr/__init__.py
new file mode 100644
index 0000000..1adf20d2
--- /dev/null
+++ b/tools/perf/wpr/__init__.py
@@ -0,0 +1,3 @@
+# 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.
diff --git a/tools/perf/wpr/update_wpr.py b/tools/perf/wpr/update_wpr.py
new file mode 100644
index 0000000..ad0dc143
--- /dev/null
+++ b/tools/perf/wpr/update_wpr.py
@@ -0,0 +1,205 @@
+# 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.
+
+# Script to automate updating existing WPR benchmarks from live versions of the
+# sites. Only supported on Mac/Linux.
+
+import argparse
+import datetime
+import os
+import re
+import shutil
+import subprocess
+import tempfile
+
+from core import cli_helpers
+
+
+SRC_ROOT = os.path.abspath(
+    os.path.join(os.path.dirname(__file__), '..', '..', '..'))
+RESULTS2JSON = os.path.join(
+    SRC_ROOT, 'third_party', 'catapult', 'tracing', 'bin', 'results2json')
+HISTOGRAM2CSV = os.path.join(
+    SRC_ROOT, 'third_party', 'catapult', 'tracing', 'bin', 'histograms2csv')
+RUN_BENCHMARK = os.path.join(SRC_ROOT, 'tools', 'perf', 'run_benchmark')
+
+
+class WprUpdater(object):
+  def __init__(self, args):
+    self.story = args.story
+    self.device_id = args.device_id
+    self.repeat = args.repeat
+    self.binary = args.binary
+    self.output_dir = tempfile.mkdtemp()
+
+  def _PrepareEnv(self):
+    # Enforce the same local settings for recording and replays on the bots.
+    env = os.environ.copy()
+    env['LC_ALL'] = 'en_US.UTF-8'
+    return env
+
+  def _Run(self, command, ok_fail=False):
+    return cli_helpers.Run(command, ok_fail=ok_fail, env=self._PrepareEnv())
+
+  def _CheckLog(self, command, log_name):
+    # This is a wrapper around cli_helpers.CheckLog that adds timestamp to the
+    # log filename and substitutes placeholders such as {src}, {name},
+    # {device_id} in the command.
+    name_regex = '^%s$' % re.escape(self.story)
+    command = [
+        c.format(src=SRC_ROOT, name=name_regex, device_id=self.device_id)
+        for c in command]
+    timestamp = datetime.datetime.now().strftime('%Y_%m_%d_%H_%M_%S')
+    log_path = os.path.join(self.output_dir, '%s_%s' % (log_name, timestamp))
+    cli_helpers.CheckLog(command, log_path=log_path, env=self._PrepareEnv())
+    return log_path
+
+  def _IsDesktop(self):
+    return self.device_id is None
+
+  def _ExtractLogFile(self, out_file):
+    # This method extracts the name of the chrome log file from the
+    # run_benchmark output log and copies it to the temporary directory next to
+    # the log file, which ensures that it is not overridden by the next run.
+    try:
+      line = subprocess.check_output(
+        ['grep', 'Chrome log file will be saved in', out_file])
+      os.rename(line.split()[-1], out_file + '.chrome.log')
+    except subprocess.CalledProcessError as e:
+      cli_helpers.Error('Could not find log file: {error}', error=e)
+
+  def _ExtractResultsFile(self, out_file):
+    results_file = out_file + '.results.html'
+    os.rename(os.path.join(self.output_dir, 'results.html'), results_file)
+
+  def _RunSystemHealthMemoryBenchmark(self, log_name, live=False):
+    args = [RUN_BENCHMARK, 'run']
+
+    if self._IsDesktop():
+      args.append('system_health.memory_desktop')
+    else:
+      args.extend('system_health.memory_mobile')
+
+    if self.binary:
+      args.extend([
+        '--browser-executable=%s' % self.binary,
+        '--browser=exact',
+      ])
+    elif self._IsDesktop():
+      args.append('--browser=system')
+    else:
+      args.append('--browser=android-system-chrome')
+
+    args.extend([
+      '--output-format=html', '--show-stdout',
+      '--reset-results', '--story-filter={name}',
+      '--browser-logging-verbosity=verbose',
+      '--pageset-repeat=%s' % self.repeat,
+      '--output-dir', self.output_dir])
+    if live:
+      args.append('--use-live-sites')
+    out_file = self._CheckLog(args, log_name=log_name)
+    self._ExtractResultsFile(out_file)
+    self._ExtractLogFile(out_file)
+    return out_file
+
+  def _PrintResultsHTMLInfo(self, out_file):
+    results_file = out_file + '.results.html'
+    histogram_json = out_file + '.hist.json'
+    histogram_csv = out_file + '.hist.csv'
+
+    self._Run([RESULTS2JSON, results_file, histogram_json])
+    self._Run([HISTOGRAM2CSV, histogram_json, histogram_csv])
+
+    cli_helpers.Info('Metrics results: file://{path}', path=results_file)
+    names = set([
+        'console:error:network',
+        'console:error:js',
+        'console:error:all',
+        'console:error:security'])
+    with open(histogram_csv) as f:
+      for line in f.readlines():
+        line = line.split(',')
+        if line[0] in names:
+          cli_helpers.Info('    %-26s%s' % ('[%s]:' % line[0], line[2]))
+
+  def _PrintRunInfo(self, out_file, results_details=True):
+    try:
+      if results_details:
+        self._PrintResultsHTMLInfo(out_file)
+    except Exception as e:
+      cli_helpers.Error('Could not print results.html tests: %s' % e)
+
+    def shell(cmd):
+      return subprocess.check_output(cmd, shell=True).rstrip()
+
+    def statsFor(name, filters='wc -l'):
+      cmd = 'grep "DevTools console .%s." "%s"' % (name, out_file)
+      cmd += ' | ' + filters
+      output = shell(cmd) or '0'
+      if len(output) > 7:
+        cli_helpers.Info('    %-26s%s' % ('[%s]:' % name, cmd))
+        cli_helpers.Info('      ' + output.replace('\n', '\n      '))
+      else:
+        cli_helpers.Info('    %-16s%-8s  %s' % ('[%s]:' % name, output, cmd))
+
+    cli_helpers.Info('Stdout/Stderr Log: %s' % out_file)
+    cli_helpers.Info('Chrome Log: %s.chrome.log' % out_file)
+    cli_helpers.Info(
+        '    Total output:   %s' %
+        subprocess.check_output(['wc', '-l', out_file]).rstrip())
+    cli_helpers.Info(
+        '    Total Console:  %s' %
+        shell('grep "DevTools console" "%s" | wc -l' % out_file))
+    statsFor('security')
+    statsFor('network', 'cut -d " " -f 20- | sort | uniq -c | sort -nr')
+
+    chrome_log = '%s.chrome.log' % out_file
+    if os.path.isfile(chrome_log):
+      cmd = 'grep "Uncaught .*Error" "%s"' % chrome_log
+      count = shell(cmd + '| wc -l')
+      cli_helpers.Info('    %-16s%-8s  %s' % ('[javascript]:', count, cmd))
+
+  def LiveRun(self):
+    cli_helpers.Step('LIVE RUN: %s' % self.story)
+    out_file = self._RunSystemHealthMemoryBenchmark(
+        log_name='live', live=True)
+    self._PrintRunInfo(out_file)
+    return out_file
+
+  def Cleanup(self):
+    if cli_helpers.Ask('Should I clean up the temp dir with logs?'):
+      shutil.rmtree(self.output_dir, ignore_errors=True)
+    else:
+      cli_helpers.Comment(
+        'No problem. All logs will remain in %s - feel free to remove that '
+        'directory when done.' % self.output_dir)
+
+
+def Main(argv):
+  parser = argparse.ArgumentParser()
+  parser.add_argument(
+      '-s', '--story', dest='story', required=True,
+      help='Benchmark story to be recorded, replayed or uploaded.')
+  parser.add_argument(
+      '-d', '--device-id', dest='device_id',
+      help='Specify the device serial number listed by `adb devices`. When not '
+           'specified, the script runs in desktop mode.')
+  parser.add_argument(
+      '--pageset-repeat', type=int, default=1, dest='repeat',
+      help='Number of times to repeat the entire pageset.')
+  parser.add_argument(
+      '--binary', default=None,
+      help='Path to the Chromium/Chrome binary relative to output directory. '
+           'Defaults to default Chrome browser installed if not specified.')
+  parser.add_argument(
+      'command', choices=['live'],
+      help='Mode in which to run this script.')
+
+  args = parser.parse_args(argv)
+
+  updater = WprUpdater(args)
+  if args.command =='live':
+    updater.LiveRun()
+  updater.Cleanup()
diff --git a/tools/perf/wpr/update_wpr_unittest.py b/tools/perf/wpr/update_wpr_unittest.py
new file mode 100644
index 0000000..e1a0592
--- /dev/null
+++ b/tools/perf/wpr/update_wpr_unittest.py
@@ -0,0 +1,159 @@
+# 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.
+
+# pylint: disable=protected-access
+
+import argparse
+import unittest
+
+import mock
+
+from wpr import update_wpr
+
+
+WPR_UPDATER = 'wpr.update_wpr.WprUpdater.'
+
+
+class UpdateWPRTest(unittest.TestCase):
+  def setUp(self):
+    self.maxDiff = None
+
+    self._check_log = mock.patch('core.cli_helpers.CheckLog').start()
+    self._run = mock.patch('core.cli_helpers.Run').start()
+    self._check_output = mock.patch('subprocess.check_output').start()
+    self._check_call = mock.patch('subprocess.check_call').start()
+    self._info = mock.patch('core.cli_helpers.Info').start()
+    self._comment = mock.patch('core.cli_helpers.Comment').start()
+    self._open = mock.patch('__builtin__.open').start()
+    datetime = mock.patch('datetime.datetime').start()
+    datetime.now.return_value.strftime.return_value = '<tstamp>'
+
+    mock.patch('tempfile.mkdtemp', return_value='/tmp/dir').start()
+    mock.patch('core.cli_helpers.Fatal').start()
+    mock.patch('core.cli_helpers.Error').start()
+    mock.patch('core.cli_helpers.Step').start()
+    mock.patch('os.environ').start().copy.return_value = {}
+    mock.patch('wpr.update_wpr.SRC_ROOT', '...').start()
+    mock.patch('wpr.update_wpr.RESULTS2JSON', '.../results2json').start()
+    mock.patch('wpr.update_wpr.HISTOGRAM2CSV', '.../histograms2csv').start()
+    mock.patch('wpr.update_wpr.RUN_BENCHMARK', '.../run_benchmark').start()
+    mock.patch('os.path.join', lambda *parts: '/'.join(parts)).start()
+
+    self.wpr_updater = update_wpr.WprUpdater(argparse.Namespace(
+      story='<story>', device_id=None, repeat=1, binary=None))
+
+  def tearDown(self):
+    mock.patch.stopall()
+
+  def testMain(self):
+    wpr_updater_cls = mock.patch('wpr.update_wpr.WprUpdater').start()
+    update_wpr.Main([
+      'live',
+      '-s', 'foo:bar:story:2019',
+      '-d', 'H2345234FC33',
+      '--binary', '<binary>'
+    ])
+    self.assertListEqual(wpr_updater_cls.mock_calls, [
+      mock.call(argparse.Namespace(
+        binary='<binary>', command='live', device_id='H2345234FC33',
+        repeat=1, story='foo:bar:story:2019')),
+      mock.call().LiveRun(),
+      mock.call().Cleanup(),
+    ])
+
+  def testCleanupManual(self):
+    mock.patch('core.cli_helpers.Ask', return_value=False).start()
+    rmtree = mock.patch('shutil.rmtree').start()
+    self.wpr_updater.Cleanup()
+    self._comment.assert_called_once_with(
+        'No problem. All logs will remain in /tmp/dir - feel free to remove '
+        'that directory when done.')
+    rmtree.assert_not_called()
+
+  def testCleanupAutomatic(self):
+    mock.patch('core.cli_helpers.Ask', return_value=True).start()
+    rmtree = mock.patch('shutil.rmtree').start()
+    self.wpr_updater.Cleanup()
+    rmtree.assert_called_once_with('/tmp/dir', ignore_errors=True)
+
+  def testLiveRun(self):
+    run_benchmark = mock.patch(
+        WPR_UPDATER + '_RunSystemHealthMemoryBenchmark',
+        return_value='<out-file>').start()
+    print_run_info = mock.patch(WPR_UPDATER + '_PrintRunInfo').start()
+    self.wpr_updater.LiveRun()
+    run_benchmark.assert_called_once_with(log_name='live', live=True)
+    print_run_info.assert_called_once_with('<out-file>')
+
+  def testRunBenchmark(self):
+    rename = mock.patch('os.rename').start()
+    self._check_output.return_value = '  <chrome-log>'
+
+    self.wpr_updater._RunSystemHealthMemoryBenchmark('<log_name>', True)
+
+    # Check correct arguments when running benchmark.
+    self._check_log.assert_called_once_with(
+        [
+          '.../run_benchmark', 'run', 'system_health.memory_desktop',
+          '--browser=system', '--output-format=html', '--show-stdout',
+          '--reset-results', '--story-filter=^\\<story\\>$',
+          '--browser-logging-verbosity=verbose', '--pageset-repeat=1',
+          '--output-dir', '/tmp/dir', '--use-live-sites'
+        ],
+        env={'LC_ALL': 'en_US.UTF-8'},
+        log_path='/tmp/dir/<log_name>_<tstamp>')
+
+    # Check logs are correctly extracted.
+    self.assertListEqual(rename.mock_calls, [
+      mock.call(
+        '/tmp/dir/results.html', '/tmp/dir/<log_name>_<tstamp>.results.html'),
+      mock.call('<chrome-log>', '/tmp/dir/<log_name>_<tstamp>.chrome.log'),
+    ])
+
+  def testPrintResultsHTMLInfo(self):
+    self._open.return_value.__enter__.return_value.readlines.return_value = [
+        'console:error:network,foo,bar',
+        'console:error:js,foo,bar',
+        'console:error:security,foo,bar',
+    ]
+    self.wpr_updater._PrintResultsHTMLInfo('<outfile>')
+    self.assertListEqual(self._run.mock_calls, [
+      mock.call(
+        ['.../results2json', '<outfile>.results.html', '<outfile>.hist.json'],
+        env={'LC_ALL': 'en_US.UTF-8'}, ok_fail=False),
+      mock.call(
+        ['.../histograms2csv', '<outfile>.hist.json', '<outfile>.hist.csv'],
+        env={'LC_ALL': 'en_US.UTF-8'}, ok_fail=False),
+    ])
+    self._open.assert_called_once_with('<outfile>.hist.csv')
+    self.assertListEqual(self._info.mock_calls, [
+      mock.call(
+        'Metrics results: file://{path}', path='<outfile>.results.html'),
+      mock.call('    [console:error:network]:  bar'),
+      mock.call('    [console:error:js]:       bar'),
+      mock.call('    [console:error:security]: bar')
+    ])
+
+  def testPrintRunInfo(self):
+    print_results = mock.patch(
+        WPR_UPDATER + '_PrintResultsHTMLInfo',
+        side_effect=[Exception()]).start()
+    self._check_output.return_value = '0\n'
+    self.wpr_updater._PrintRunInfo('<outfile>', True)
+    print_results.assert_called_once_with('<outfile>')
+    self.assertListEqual(self._info.mock_calls, [
+      mock.call('Stdout/Stderr Log: <outfile>'),
+      mock.call('Chrome Log: <outfile>.chrome.log'),
+      mock.call('    Total output:   0'),
+      mock.call('    Total Console:  0'),
+      mock.call('    [security]:     0         grep "DevTools console '
+                '.security." "<outfile>" | wc -l'),
+      mock.call('    [network]:      0         grep "DevTools console '
+                '.network." "<outfile>" | cut -d " " -f 20- | sort | uniq -c '
+                '| sort -nr'),
+    ])
+
+
+if __name__ == "__main__":
+  unittest.main()
diff --git a/tools/run-swarmed.py b/tools/run-swarmed.py
index ef7097e..e81a2e8 100755
--- a/tools/run-swarmed.py
+++ b/tools/run-swarmed.py
@@ -33,21 +33,20 @@
   """Triggers a swarming job. The arguments passed are:
   - The index of the job;
   - The command line arguments object;
-  - The hash of the isolate job used to trigger;
-  - Value of --gtest_filter arg, or empty if none.
+  - The hash of the isolate job used to trigger.
 
   The return value is passed to a collect-style map() and consists of:
   - The index of the job;
   - The json file created by triggering and used to collect results;
   - The command line arguments object.
   """
-  index, args, isolated_hash, gtest_filter = args
+  index, args, isolated_hash = args
   json_file = os.path.join(args.results, '%d.json' % index)
   trigger_args = [
       'tools/swarming_client/swarming.py', 'trigger',
       '-S', 'https://chromium-swarm.appspot.com',
       '-I', 'https://isolateserver.appspot.com',
-      '-d', 'pool', 'Chrome',
+      '-d', 'pool', args.pool,
       '-s', isolated_hash,
       '--dump-json', json_file,
   ]
@@ -60,12 +59,37 @@
     ]
   elif args.target_os == 'win':
     trigger_args += [ '-d', 'os', 'Windows' ]
+  elif args.target_os == 'android':
+    # The canonical version numbers are stored in the infra repository here:
+    # build/scripts/slave/recipe_modules/swarming/api.py
+    cpython_version = 'version:2.7.14.chromium14'
+    vpython_version = 'git_revision:96f81e737868d43124b4661cf1c325296ca04944'
+    cpython_pkg = (
+        '.swarming_module:infra/python/cpython/${platform}:' +
+        cpython_version)
+    vpython_native_pkg = (
+        '.swarming_module:infra/tools/luci/vpython-native/${platform}:' +
+        vpython_version)
+    vpython_pkg = (
+        '.swarming_module:infra/tools/luci/vpython/${platform}:' +
+        vpython_version)
+    trigger_args += [
+        '-d', 'os', 'Android',
+        '-d', 'device_os', args.device_os,
+        '--cipd-package', cpython_pkg,
+        '--cipd-package', vpython_native_pkg,
+        '--cipd-package', vpython_pkg,
+        '--env-prefix', 'PATH', '.swarming_module',
+        '--env-prefix', 'PATH', '.swarming_module/bin',
+        '--env-prefix', 'VPYTHON_VIRTUALENV_ROOT',
+        '.swarming_module_cache/vpython',
+    ]
   trigger_args += [
       '--',
       '--test-launcher-summary-output=${ISOLATED_OUTDIR}/output.json',
       '--system-log-file=${ISOLATED_OUTDIR}/system_log']
-  if gtest_filter:
-    trigger_args.append('--gtest_filter=' + gtest_filter)
+  if args.gtest_filter:
+    trigger_args.append('--gtest_filter=' + args.gtest_filter)
   elif args.target_os == 'fuchsia':
     filter_file = \
         'testing/buildbot/filters/fuchsia.' + args.test_name + '.filter'
@@ -108,6 +132,10 @@
                       help='CPU architecture of the test binary.')
   parser.add_argument('--copies', '-n', type=int, default=1,
                       help='Number of copies to spawn.')
+  parser.add_argument('--device-os', default='M',
+                      help='Run tests on the given version of Android.')
+  parser.add_argument('--pool', default='Chrome',
+                      help='Use the given swarming pool.')
   parser.add_argument('--results', '-r', default='results',
                       help='Directory in which to store results.')
   parser.add_argument('--gtest_filter',
@@ -160,8 +188,7 @@
   try:
     print 'Triggering %d tasks...' % args.copies
     pool = multiprocessing.Pool()
-    spawn_args = map(lambda i: (i, args, isolated_hash, args.gtest_filter),
-                     range(args.copies))
+    spawn_args = map(lambda i: (i, args, isolated_hash), range(args.copies))
     spawn_results = pool.imap_unordered(_Spawn, spawn_args)
 
     exit_codes = []
diff --git a/tools/ubsan/blacklist.txt b/tools/ubsan/blacklist.txt
index 1c965ae..68e25a21 100644
--- a/tools/ubsan/blacklist.txt
+++ b/tools/ubsan/blacklist.txt
@@ -7,33 +7,23 @@
 src:*/third_party/yasm/*
 
 #############################################################################
-# V8 gives too many false positives. Ignore them for now.
-src:*/v8/*
-
-#############################################################################
 # Ignore system libraries.
 src:*/usr/*
 
 #############################################################################
-# V8 UBsan supressions, commented out for now since we are ignorning v8
-# completely.
-# fun:*v8*internal*FastD2I*
-# fun:*v8*internal*ComputeIntegerHash*
-# fun:*v8*internal*ComputeLongHash*
-# fun:*v8*internal*ComputePointerHash*
-# src:*/v8/src/base/bits.cc
-# src:*/v8/src/base/functional.cc
-# Undefined behaviour (integer overflow) is expected but ignored in this
-# function.
-# fun:*JsonParser*ParseJsonNumber*
+# V8 UBsan supressions
+# UBSan bug, fixed in LLVM r350779. Drop this suppression when that
+# revision has rolled into Chromium's bundled Clang.
+fun:*v8*internal*NewArray*
 
-# Runtime numeric functions.
-# src:*/v8/src/runtime/runtime-numbers.cc
+# Bug v8:8735: PropertyCallbackInfo<void> vs PropertyCallbackInfo<T>.
+fun:*v8*internal*PropertyCallbackArguments*CallAccessorSetter*
+fun:*v8*internal*PropertyCallbackArguments*BasicCallNamedGetterCallback*
+fun:*v8*internal*InvokeAccessorGetterCallback*
 
-# Shifts of negative numbers
-# fun:*v8*internal*HPositionInfo*TagPosition*
-# fun:*v8*internal*Range*Shl*
-# fun:*v8*internal*RelocInfoWriter*WriteTaggedData*
+# Bug v8:8735: WeakCallbackInfo<void> vs. WeakCallbackInfo<T>.
+fun:*v8*internal*GlobalHandles*PendingPhantomCallback*Invoke*
+fun:*v8*internal*GlobalHandles*Node*PostGarbageCollectionProcessing*
 
 #############################################################################
 # Undefined arithmetic that can be safely ignored.
diff --git a/tools/web_dev_style/js_checker.py b/tools/web_dev_style/js_checker.py
index 263867f..8638786 100644
--- a/tools/web_dev_style/js_checker.py
+++ b/tools/web_dev_style/js_checker.py
@@ -50,7 +50,7 @@
 
   def PolymerLocalIdCheck(self, i, line):
     """Checks for use of element.$.localId."""
-    return self.RegexCheck(i, line, r"(?<!this)(\.\$)[\[\.]",
+    return self.RegexCheck(i, line, r"(?<!this)(\.\$)[\[\.](?![a-zA-Z]+\()",
         "Please only use this.$.localId, not element.$.localId")
 
   def RunEsLintChecks(self, affected_js_files, format='stylish'):
diff --git a/tools/web_dev_style/js_checker_test.py b/tools/web_dev_style/js_checker_test.py
index b5a4ced..ce62e24 100755
--- a/tools/web_dev_style/js_checker_test.py
+++ b/tools/web_dev_style/js_checker_test.py
@@ -207,6 +207,7 @@
         "this.$.id",
         "this.$.localId",
         "this.$['fancy-id']",
+        "this.page.$.flushForTesting()",
     ]
     for line in lines:
       self.ShouldPassPolymerLocalIdCheck(line)
diff --git a/ui/gfx/vsync_provider.cc b/ui/gfx/vsync_provider.cc
index 6276b7f..ff4b4a5 100644
--- a/ui/gfx/vsync_provider.cc
+++ b/ui/gfx/vsync_provider.cc
@@ -6,9 +6,8 @@
 
 namespace gfx {
 
-void FixedVSyncProvider::GetVSyncParameters(
-    const UpdateVSyncCallback& callback) {
-  callback.Run(timebase_, interval_);
+void FixedVSyncProvider::GetVSyncParameters(UpdateVSyncCallback callback) {
+  std::move(callback).Run(timebase_, interval_);
 }
 
 bool FixedVSyncProvider::GetVSyncParametersIfAvailable(
diff --git a/ui/gfx/vsync_provider.h b/ui/gfx/vsync_provider.h
index e8aa0fe7..87b0a15 100644
--- a/ui/gfx/vsync_provider.h
+++ b/ui/gfx/vsync_provider.h
@@ -15,8 +15,8 @@
  public:
   virtual ~VSyncProvider() {}
 
-  typedef base::Callback<
-      void(const base::TimeTicks timebase, const base::TimeDelta interval)>
+  typedef base::OnceCallback<void(const base::TimeTicks timebase,
+                                  const base::TimeDelta interval)>
       UpdateVSyncCallback;
 
   // Get the time of the most recent screen refresh, along with the time
@@ -25,7 +25,7 @@
   // later via a PostTask to the current MessageLoop, or never (if we have
   // no data source). We provide the strong guarantee that the callback will
   // not be called once the instance of this class is destroyed.
-  virtual void GetVSyncParameters(const UpdateVSyncCallback& callback) = 0;
+  virtual void GetVSyncParameters(UpdateVSyncCallback callback) = 0;
 
   // Similar to GetVSyncParameters(). It returns true, if the data is available.
   // Otherwise false is returned.
@@ -48,7 +48,7 @@
 
   ~FixedVSyncProvider() override {}
 
-  void GetVSyncParameters(const UpdateVSyncCallback& callback) override;
+  void GetVSyncParameters(UpdateVSyncCallback callback) override;
   bool GetVSyncParametersIfAvailable(base::TimeTicks* timebase,
                                      base::TimeDelta* interval) override;
   bool SupportGetVSyncParametersIfAvailable() const override;
diff --git a/ui/gl/gl_surface_glx.cc b/ui/gl/gl_surface_glx.cc
index 2eab605a..53766f81 100644
--- a/ui/gl/gl_surface_glx.cc
+++ b/ui/gl/gl_surface_glx.cc
@@ -266,8 +266,7 @@
     }
   }
 
-  void GetVSyncParameters(
-      const gfx::VSyncProvider::UpdateVSyncCallback& callback) {
+  void GetVSyncParameters(gfx::VSyncProvider::UpdateVSyncCallback callback) {
     base::TimeTicks now;
     {
       // Don't allow |window_| destruction while we're probing vsync.
@@ -291,8 +290,8 @@
     const base::TimeDelta kDefaultInterval =
         base::TimeDelta::FromSeconds(1) / 60;
 
-    task_runner_->PostTask(FROM_HERE,
-                           base::BindOnce(callback, now, kDefaultInterval));
+    task_runner_->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), now, kDefaultInterval));
   }
 
  private:
@@ -344,11 +343,11 @@
   }
 
   void GetVSyncParameters(
-      const gfx::VSyncProvider::UpdateVSyncCallback& callback) override {
+      gfx::VSyncProvider::UpdateVSyncCallback callback) override {
     // Only one outstanding request per surface.
     if (!pending_callback_) {
       DCHECK(callback);
-      pending_callback_ = callback;
+      pending_callback_ = std::move(callback);
       vsync_thread_->task_runner()->PostTask(
           FROM_HERE,
           base::BindOnce(&SGIVideoSyncProviderThreadShim::GetVSyncParameters,
diff --git a/ui/gl/sync_control_vsync_provider.cc b/ui/gl/sync_control_vsync_provider.cc
index 2b94e43a..6e49834 100644
--- a/ui/gl/sync_control_vsync_provider.cc
+++ b/ui/gl/sync_control_vsync_provider.cc
@@ -37,11 +37,11 @@
 SyncControlVSyncProvider::~SyncControlVSyncProvider() {}
 
 void SyncControlVSyncProvider::GetVSyncParameters(
-    const UpdateVSyncCallback& callback) {
+    UpdateVSyncCallback callback) {
   base::TimeTicks timebase;
   base::TimeDelta interval;
   if (GetVSyncParametersIfAvailable(&timebase, &interval))
-    callback.Run(timebase, interval);
+    std::move(callback).Run(timebase, interval);
 }
 
 bool SyncControlVSyncProvider::GetVSyncParametersIfAvailable(
diff --git a/ui/gl/sync_control_vsync_provider.h b/ui/gl/sync_control_vsync_provider.h
index 735d7ee..d958a2b2 100644
--- a/ui/gl/sync_control_vsync_provider.h
+++ b/ui/gl/sync_control_vsync_provider.h
@@ -20,7 +20,7 @@
   SyncControlVSyncProvider();
   ~SyncControlVSyncProvider() override;
 
-  void GetVSyncParameters(const UpdateVSyncCallback& callback) override;
+  void GetVSyncParameters(UpdateVSyncCallback callback) override;
   bool GetVSyncParametersIfAvailable(base::TimeTicks* timebase,
                                      base::TimeDelta* interval) override;
   bool SupportGetVSyncParametersIfAvailable() const override;
diff --git a/ui/gl/vsync_provider_win.cc b/ui/gl/vsync_provider_win.cc
index 34dc0077..918c0d1 100644
--- a/ui/gl/vsync_provider_win.cc
+++ b/ui/gl/vsync_provider_win.cc
@@ -29,11 +29,11 @@
   ::LoadLibrary(L"dwmapi.dll");
 }
 
-void VSyncProviderWin::GetVSyncParameters(const UpdateVSyncCallback& callback) {
+void VSyncProviderWin::GetVSyncParameters(UpdateVSyncCallback callback) {
   base::TimeTicks timebase;
   base::TimeDelta interval;
   if (GetVSyncParametersIfAvailable(&timebase, &interval))
-    callback.Run(timebase, interval);
+    std::move(callback).Run(timebase, interval);
 }
 
 bool VSyncProviderWin::GetVSyncParametersIfAvailable(
diff --git a/ui/gl/vsync_provider_win.h b/ui/gl/vsync_provider_win.h
index 3ee9a29..00b1ef2e 100644
--- a/ui/gl/vsync_provider_win.h
+++ b/ui/gl/vsync_provider_win.h
@@ -19,7 +19,7 @@
   static void InitializeOneOff();
 
   // gfx::VSyncProvider overrides;
-  void GetVSyncParameters(const UpdateVSyncCallback& callback) override;
+  void GetVSyncParameters(UpdateVSyncCallback callback) override;
   bool GetVSyncParametersIfAvailable(base::TimeTicks* timebase,
                                      base::TimeDelta* interval) override;
   bool SupportGetVSyncParametersIfAvailable() const override;
diff --git a/ui/keyboard/public/keyboard_switches.cc b/ui/keyboard/public/keyboard_switches.cc
index f99e8e95..261acac 100644
--- a/ui/keyboard/public/keyboard_switches.cc
+++ b/ui/keyboard/public/keyboard_switches.cc
@@ -10,7 +10,6 @@
 const char kDisableInputView[] = "disable-input-view";
 const char kDisableVoiceInput[] = "disable-voice-input";
 const char kDisableGestureTyping[] = "disable-gesture-typing";
-const char kDisableGestureEditing[] = "disable-gesture-editing";
 const char kEnableVirtualKeyboard[] = "enable-virtual-keyboard";
 const char kDisableVirtualKeyboardOverscroll[] =
     "disable-virtual-keyboard-overscroll";
diff --git a/ui/keyboard/public/keyboard_switches.h b/ui/keyboard/public/keyboard_switches.h
index ae172d5..a2ac6b7 100644
--- a/ui/keyboard/public/keyboard_switches.h
+++ b/ui/keyboard/public/keyboard_switches.h
@@ -20,10 +20,6 @@
 // Flag which disables gesture typing for the virtual keyboard.
 KEYBOARD_EXPORT extern const char kDisableGestureTyping[];
 
-// Controls the appearance of the settings option to enable gesture editing
-// for the virtual keyboard.
-KEYBOARD_EXPORT extern const char kDisableGestureEditing[];
-
 // Enables the virtual keyboard.
 KEYBOARD_EXPORT extern const char kEnableVirtualKeyboard[];
 
diff --git a/ui/login/account_picker/md_screen_account_picker.css b/ui/login/account_picker/chromeos_screen_account_picker.css
similarity index 100%
rename from ui/login/account_picker/md_screen_account_picker.css
rename to ui/login/account_picker/chromeos_screen_account_picker.css
diff --git a/ui/login/account_picker/md_screen_account_picker.html b/ui/login/account_picker/chromeos_screen_account_picker.html
similarity index 100%
rename from ui/login/account_picker/md_screen_account_picker.html
rename to ui/login/account_picker/chromeos_screen_account_picker.html
diff --git a/ui/login/account_picker/md_screen_account_picker.js b/ui/login/account_picker/chromeos_screen_account_picker.js
similarity index 99%
rename from ui/login/account_picker/md_screen_account_picker.js
rename to ui/login/account_picker/chromeos_screen_account_picker.js
index 58f36a968..931b0d8 100644
--- a/ui/login/account_picker/md_screen_account_picker.js
+++ b/ui/login/account_picker/chromeos_screen_account_picker.js
@@ -388,7 +388,7 @@
      * Sets the authentication type used to authenticate the user.
      * @param {string} username Username of selected user
      * @param {number} authType Authentication type, must be a valid value in
-     *                          the AUTH_TYPE enum in user_pod_row.js.
+     *                          the AUTH_TYPE enum in chromeos_user_pod_row.js.
      * @param {string} value The initial value to use for authentication.
      */
     setAuthType: function(username, authType, value) {
diff --git a/ui/login/account_picker/md_user_pod_row.css b/ui/login/account_picker/chromeos_user_pod_row.css
similarity index 100%
rename from ui/login/account_picker/md_user_pod_row.css
rename to ui/login/account_picker/chromeos_user_pod_row.css
diff --git a/ui/login/account_picker/md_user_pod_row.js b/ui/login/account_picker/chromeos_user_pod_row.js
similarity index 100%
rename from ui/login/account_picker/md_user_pod_row.js
rename to ui/login/account_picker/chromeos_user_pod_row.js
diff --git a/ui/login/account_picker/md_user_pod_template.css b/ui/login/account_picker/chromeos_user_pod_template.css
similarity index 96%
rename from ui/login/account_picker/md_user_pod_template.css
rename to ui/login/account_picker/chromeos_user_pod_template.css
index 9679433..83bc20f 100644
--- a/ui/login/account_picker/md_user_pod_template.css
+++ b/ui/login/account_picker/chromeos_user_pod_template.css
@@ -3,7 +3,7 @@
  * found in the LICENSE file.
  *
  * This is the stylesheet imported into the style module in
- * user_pod_template.html.
+ * chromeos_user_pod_template.html.
  */
 
 .action-box-remove-user-warning .remove-warning-button {
diff --git a/ui/login/account_picker/md_user_pod_template.html b/ui/login/account_picker/chromeos_user_pod_template.html
similarity index 99%
rename from ui/login/account_picker/md_user_pod_template.html
rename to ui/login/account_picker/chromeos_user_pod_template.html
index 2ce17750..0161e8d 100644
--- a/ui/login/account_picker/md_user_pod_template.html
+++ b/ui/login/account_picker/chromeos_user_pod_template.html
@@ -1,6 +1,6 @@
 <dom-module id="user-pod-template-shared-styles">
   <template>
-    <link rel="stylesheet" href="md_user_pod_template.css">
+    <link rel="stylesheet" href="chromeos_user_pod_template.css">
   </template>
 </dom-module>