diff --git a/DEPS b/DEPS
index c840c25..cf95f5a2 100644
--- a/DEPS
+++ b/DEPS
@@ -138,7 +138,7 @@
   # 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': '82310316135997d5a0c3d9750b72dbc184c18829',
+  'skia_revision': 'fe18de506097d9cfa5f2a2a057e1dd134a96c116',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -154,7 +154,7 @@
   # 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': '8ac0bd6cc3e5aaabb904ffe01639ecea4b7b5fde',
+  'swiftshader_revision': 'c2c829bc9a633302d6a6b19e635a1ab6e7ae5915',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # 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 freetype
   # and whatever else without interference from each other.
-  'freetype_revision': 'c949ab0757a2514cd3a37b3e1e8390fd662a025b',
+  'freetype_revision': '711b593e4b589fbd726a4962ad492fc4e416355d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling HarfBuzz
   # and whatever else without interference from each other.
@@ -201,7 +201,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': '991302572440fa546ef523789fa821943184b4bf',
+  'catapult_revision': 'abea78f985f4f3156889f26ba5e34131ed457437',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -273,7 +273,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': 'fa7228a1fa63163a6f27b9b0a9036d1be27c6dd6',
+  'dawn_revision': '120f5d906283d44c5259987f650bc9c8e5b50d08',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -1187,7 +1187,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' +  'b4697aa45589d116d9db40a21de69d1365dd7e1c',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' +  'c63be46c502089deed3fc93217425e4b6ea2f517',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1396,7 +1396,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@104f3fc6931f2aeb29ad8445345d513b0259dfb8',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@b793b75db94826ef7a42406d267d720d59c22354',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/ash/accelerators/accelerator_controller_impl.cc b/ash/accelerators/accelerator_controller_impl.cc
index cdc0904..df182d8 100644
--- a/ash/accelerators/accelerator_controller_impl.cc
+++ b/ash/accelerators/accelerator_controller_impl.cc
@@ -918,7 +918,6 @@
   // prefs iteself.
   Shell* shell = Shell::Get();
   shell->accessibility_controller()->SetFullscreenMagnifierEnabled(enabled);
-  shell->magnification_controller()->SetEnabled(enabled);
 }
 
 void SetHighContrastEnabled(bool enabled) {
diff --git a/ash/accelerators/accelerator_controller_unittest.cc b/ash/accelerators/accelerator_controller_unittest.cc
index dee5465..e2dc7c7 100644
--- a/ash/accelerators/accelerator_controller_unittest.cc
+++ b/ash/accelerators/accelerator_controller_unittest.cc
@@ -22,6 +22,7 @@
 #include "ash/magnifier/magnification_controller.h"
 #include "ash/media/media_controller_impl.h"
 #include "ash/public/cpp/ash_features.h"
+#include "ash/public/cpp/ash_pref_names.h"
 #include "ash/public/cpp/ash_switches.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/public/cpp/test/shell_test_api.h"
@@ -52,6 +53,8 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/metrics/user_action_tester.h"
 #include "base/test/scoped_feature_list.h"
+#include "components/prefs/pref_change_registrar.h"
+#include "components/prefs/pref_service.h"
 #include "media/base/media_switches.h"
 #include "services/media_session/public/cpp/test/test_media_controller.h"
 #include "services/media_session/public/mojom/media_session.mojom.h"
@@ -1823,6 +1826,8 @@
 
 namespace {
 
+constexpr char kUserEmail[] = "user@magnifier";
+
 class MagnifiersAcceleratorsTester : public AcceleratorControllerTest {
  public:
   MagnifiersAcceleratorsTester() = default;
@@ -1836,13 +1841,55 @@
     return Shell::Get()->magnification_controller();
   }
 
+  PrefService* user_pref_service() {
+    return Shell::Get()->session_controller()->GetUserPrefServiceForUser(
+        AccountId::FromUserEmail(kUserEmail));
+  }
+
+  void SetUp() override {
+    AcceleratorControllerTest::SetUp();
+
+    // Create user session and simulate its login.
+    SimulateUserLogin(kUserEmail);
+  }
+
  private:
   DISALLOW_COPY_AND_ASSIGN(MagnifiersAcceleratorsTester);
 };
 
 }  // namespace
 
+// TODO (afakhry): Remove this class after refactoring MagnificationManager.
+// Mocked chrome/browser/chromeos/accessibility/magnification_manager.cc
+class FakeMagnificationManager {
+ public:
+  FakeMagnificationManager() = default;
+
+  void SetPrefs(PrefService* prefs) {
+    pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
+    pref_change_registrar_->Init(prefs);
+    pref_change_registrar_->Add(
+        prefs::kAccessibilityScreenMagnifierEnabled,
+        base::BindRepeating(&FakeMagnificationManager::UpdateMagnifierFromPrefs,
+                            base::Unretained(this)));
+    prefs_ = prefs;
+  }
+
+  void UpdateMagnifierFromPrefs() {
+    Shell::Get()->magnification_controller()->SetEnabled(
+        prefs_->GetBoolean(prefs::kAccessibilityScreenMagnifierEnabled));
+  }
+
+ private:
+  std::unique_ptr<PrefChangeRegistrar> pref_change_registrar_;
+  PrefService* prefs_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeMagnificationManager);
+};
+
 TEST_F(MagnifiersAcceleratorsTester, TestToggleFullscreenMagnifier) {
+  FakeMagnificationManager manager;
+  manager.SetPrefs(user_pref_service());
   EXPECT_FALSE(docked_magnifier_controller()->GetEnabled());
   EXPECT_FALSE(fullscreen_magnifier_controller()->IsEnabled());
   EXPECT_FALSE(IsConfirmationDialogOpen());
diff --git a/ash/multi_device_setup/multi_device_notification_presenter.cc b/ash/multi_device_setup/multi_device_notification_presenter.cc
index 4e7f2dd1..46edd07 100644
--- a/ash/multi_device_setup/multi_device_notification_presenter.cc
+++ b/ash/multi_device_setup/multi_device_notification_presenter.cc
@@ -56,18 +56,6 @@
   NOTREACHED();
 }
 
-MultiDeviceNotificationPresenter::OpenUiDelegate::~OpenUiDelegate() = default;
-
-void MultiDeviceNotificationPresenter::OpenUiDelegate::
-    OpenMultiDeviceSetupUi() {
-  Shell::Get()->system_tray_model()->client()->ShowMultiDeviceSetup();
-}
-
-void MultiDeviceNotificationPresenter::OpenUiDelegate::
-    OpenConnectedDevicesSettings() {
-  Shell::Get()->system_tray_model()->client()->ShowConnectedDevicesSettings();
-}
-
 // static
 MultiDeviceNotificationPresenter::NotificationType
 MultiDeviceNotificationPresenter::GetMetricValueForNotification(
@@ -91,7 +79,6 @@
     : message_center_(message_center),
       connector_(connector),
       binding_(this),
-      open_ui_delegate_(std::make_unique<OpenUiDelegate>()),
       weak_ptr_factory_(this) {
   DCHECK(message_center_);
   DCHECK(connector_);
@@ -191,14 +178,17 @@
                             kNotificationTypeMax);
   switch (notification_status_) {
     case Status::kNewUserNotificationVisible:
-      open_ui_delegate_->OpenMultiDeviceSetupUi();
+      Shell::Get()->system_tray_model()->client()->ShowMultiDeviceSetup();
       break;
     case Status::kExistingUserHostSwitchedNotificationVisible:
       // Clicks on the 'host switched' and 'Chromebook added' notifications have
       // the same effect, i.e. opening the Settings subpage.
       FALLTHROUGH;
     case Status::kExistingUserNewChromebookNotificationVisible:
-      open_ui_delegate_->OpenConnectedDevicesSettings();
+      Shell::Get()
+          ->system_tray_model()
+          ->client()
+          ->ShowConnectedDevicesSettings();
       break;
     case Status::kNoNotificationVisible:
       NOTREACHED();
diff --git a/ash/multi_device_setup/multi_device_notification_presenter.h b/ash/multi_device_setup/multi_device_notification_presenter.h
index 4d9571f..77bc760 100644
--- a/ash/multi_device_setup/multi_device_notification_presenter.h
+++ b/ash/multi_device_setup/multi_device_notification_presenter.h
@@ -84,16 +84,6 @@
   // MultiDevice setup notification ID.
   static const char kNotificationId[];
 
-  // These methods are delegated to a nested class to make them easier to stub
-  // in unit tests. This way they can all be stubbed simultaneously by building
-  // a test delegate class deriving from OpenUiDelegate.
-  class OpenUiDelegate {
-   public:
-    virtual ~OpenUiDelegate();
-    virtual void OpenMultiDeviceSetupUi();
-    virtual void OpenConnectedDevicesSettings();
-  };
-
   // Represents each possible MultiDevice setup notification that the setup flow
   // can show with a "none" option for the general state with no notification
   // present.
@@ -141,7 +131,6 @@
   mojo::Binding<chromeos::multidevice_setup::mojom::AccountStatusChangeDelegate>
       binding_;
 
-  std::unique_ptr<OpenUiDelegate> open_ui_delegate_;
   base::WeakPtrFactory<MultiDeviceNotificationPresenter> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(MultiDeviceNotificationPresenter);
diff --git a/ash/multi_device_setup/multi_device_notification_presenter_unittest.cc b/ash/multi_device_setup/multi_device_notification_presenter_unittest.cc
index 2cd617a..b212a44 100644
--- a/ash/multi_device_setup/multi_device_notification_presenter_unittest.cc
+++ b/ash/multi_device_setup/multi_device_notification_presenter_unittest.cc
@@ -8,10 +8,10 @@
 #include <memory>
 #include <utility>
 
+#include "ash/public/cpp/test/test_system_tray_client.h"
 #include "ash/session/test_session_controller_client.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/test/ash_test_base.h"
-#include "ash/test/ash_test_helper.h"
 #include "base/bind.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
@@ -94,43 +94,12 @@
 
 class MultiDeviceNotificationPresenterTest : public NoSessionAshTestBase {
  public:
-  class TestOpenUiDelegate
-      : public MultiDeviceNotificationPresenter::OpenUiDelegate {
-   public:
-    TestOpenUiDelegate() = default;
-    ~TestOpenUiDelegate() override = default;
-
-    int open_multi_device_setup_ui_count() const {
-      return open_multi_device_setup_ui_count_;
-    }
-
-    int open_connected_devices_settings_count() const {
-      return open_connected_devices_settings_count_;
-    }
-
-    // MultiDeviceNotificationPresenter::OpenUiDelegate:
-    void OpenMultiDeviceSetupUi() override {
-      ++open_multi_device_setup_ui_count_;
-    }
-
-    void OpenConnectedDevicesSettings() override {
-      ++open_connected_devices_settings_count_;
-    }
-
-   private:
-    int open_multi_device_setup_ui_count_ = 0;
-    int open_connected_devices_settings_count_ = 0;
-  };
-
- protected:
   MultiDeviceNotificationPresenterTest() = default;
 
   void SetUp() override {
     NoSessionAshTestBase::SetUp();
 
-    std::unique_ptr<TestOpenUiDelegate> test_open_ui_delegate =
-        std::make_unique<TestOpenUiDelegate>();
-    test_open_ui_delegate_ = test_open_ui_delegate.get();
+    test_system_tray_client_ = GetSystemTrayClient();
 
     service_manager::mojom::ConnectorRequest request;
     connector_ = service_manager::Connector::Create(&request);
@@ -150,8 +119,6 @@
     notification_presenter_ =
         std::make_unique<MultiDeviceNotificationPresenter>(
             &test_message_center_, connector_.get());
-    notification_presenter_->open_ui_delegate_ =
-        std::move(test_open_ui_delegate);
   }
 
   void TearDown() override {
@@ -271,7 +238,7 @@
   }
 
   base::HistogramTester histogram_tester_;
-  TestOpenUiDelegate* test_open_ui_delegate_;
+  TestSystemTrayClient* test_system_tray_client_;
   TestMessageCenter test_message_center_;
   std::unique_ptr<service_manager::Connector> connector_;
   std::unique_ptr<chromeos::multidevice_setup::FakeMultiDeviceSetup>
@@ -354,7 +321,7 @@
   notification_presenter_->RemoveMultiDeviceSetupNotification();
   VerifyNoNotificationIsVisible();
 
-  EXPECT_EQ(test_open_ui_delegate_->open_multi_device_setup_ui_count(), 0);
+  EXPECT_EQ(test_system_tray_client_->show_multi_device_setup_count(), 0);
   AssertPotentialHostBucketCount("MultiDeviceSetup_NotificationClicked", 0);
   AssertPotentialHostBucketCount("MultiDeviceSetup_NotificationShown", 1);
 }
@@ -369,7 +336,7 @@
   ClickNotification();
   VerifyNoNotificationIsVisible();
 
-  EXPECT_EQ(test_open_ui_delegate_->open_multi_device_setup_ui_count(), 1);
+  EXPECT_EQ(test_system_tray_client_->show_multi_device_setup_count(), 1);
   AssertPotentialHostBucketCount("MultiDeviceSetup_NotificationClicked", 1);
   AssertPotentialHostBucketCount("MultiDeviceSetup_NotificationShown", 1);
 }
@@ -384,7 +351,7 @@
   DismissNotification(true /* by_user */);
   VerifyNoNotificationIsVisible();
 
-  EXPECT_EQ(test_open_ui_delegate_->open_multi_device_setup_ui_count(), 0);
+  EXPECT_EQ(test_system_tray_client_->show_multi_device_setup_count(), 0);
   AssertPotentialHostBucketCount("MultiDeviceSetup_NotificationDismissed", 1);
 
   ShowNewUserNotification();
@@ -393,7 +360,7 @@
   DismissNotification(false /* by_user */);
   VerifyNoNotificationIsVisible();
 
-  EXPECT_EQ(test_open_ui_delegate_->open_multi_device_setup_ui_count(), 0);
+  EXPECT_EQ(test_system_tray_client_->show_multi_device_setup_count(), 0);
   AssertPotentialHostBucketCount("MultiDeviceSetup_NotificationDismissed", 1);
 }
 
@@ -406,7 +373,7 @@
   TriggerNoLongerNewUserEvent();
   VerifyNoNotificationIsVisible();
 
-  EXPECT_EQ(test_open_ui_delegate_->open_multi_device_setup_ui_count(), 0);
+  EXPECT_EQ(test_system_tray_client_->show_multi_device_setup_count(), 0);
   AssertPotentialHostBucketCount("MultiDeviceSetup_NotificationClicked", 0);
   AssertPotentialHostBucketCount("MultiDeviceSetup_NotificationShown", 1);
 }
@@ -421,7 +388,8 @@
   notification_presenter_->RemoveMultiDeviceSetupNotification();
   VerifyNoNotificationIsVisible();
 
-  EXPECT_EQ(test_open_ui_delegate_->open_connected_devices_settings_count(), 0);
+  EXPECT_EQ(test_system_tray_client_->show_connected_devices_settings_count(),
+            0);
   AssertHostSwitchedBucketCount("MultiDeviceSetup_NotificationClicked", 0);
   AssertHostSwitchedBucketCount("MultiDeviceSetup_NotificationShown", 1);
 }
@@ -436,7 +404,8 @@
   ClickNotification();
   VerifyNoNotificationIsVisible();
 
-  EXPECT_EQ(test_open_ui_delegate_->open_connected_devices_settings_count(), 1);
+  EXPECT_EQ(test_system_tray_client_->show_connected_devices_settings_count(),
+            1);
   AssertHostSwitchedBucketCount("MultiDeviceSetup_NotificationClicked", 1);
   AssertHostSwitchedBucketCount("MultiDeviceSetup_NotificationShown", 1);
 }
@@ -451,7 +420,7 @@
   DismissNotification(true /* by_user */);
   VerifyNoNotificationIsVisible();
 
-  EXPECT_EQ(test_open_ui_delegate_->open_multi_device_setup_ui_count(), 0);
+  EXPECT_EQ(test_system_tray_client_->show_multi_device_setup_count(), 0);
   AssertHostSwitchedBucketCount("MultiDeviceSetup_NotificationDismissed", 1);
 
   ShowExistingUserHostSwitchedNotification();
@@ -460,7 +429,7 @@
   DismissNotification(false /* by_user */);
   VerifyNoNotificationIsVisible();
 
-  EXPECT_EQ(test_open_ui_delegate_->open_multi_device_setup_ui_count(), 0);
+  EXPECT_EQ(test_system_tray_client_->show_multi_device_setup_count(), 0);
   AssertHostSwitchedBucketCount("MultiDeviceSetup_NotificationDismissed", 1);
 }
 
@@ -475,7 +444,8 @@
   notification_presenter_->RemoveMultiDeviceSetupNotification();
   VerifyNoNotificationIsVisible();
 
-  EXPECT_EQ(test_open_ui_delegate_->open_connected_devices_settings_count(), 0);
+  EXPECT_EQ(test_system_tray_client_->show_connected_devices_settings_count(),
+            0);
   AssertNewChromebookBucketCount("MultiDeviceSetup_NotificationClicked", 0);
   AssertNewChromebookBucketCount("MultiDeviceSetup_NotificationShown", 1);
 }
@@ -490,7 +460,8 @@
   ClickNotification();
   VerifyNoNotificationIsVisible();
 
-  EXPECT_EQ(test_open_ui_delegate_->open_connected_devices_settings_count(), 1);
+  EXPECT_EQ(test_system_tray_client_->show_connected_devices_settings_count(),
+            1);
   AssertNewChromebookBucketCount("MultiDeviceSetup_NotificationClicked", 1);
   AssertNewChromebookBucketCount("MultiDeviceSetup_NotificationShown", 1);
 }
@@ -506,7 +477,7 @@
   DismissNotification(true /* by_user */);
   VerifyNoNotificationIsVisible();
 
-  EXPECT_EQ(test_open_ui_delegate_->open_multi_device_setup_ui_count(), 0);
+  EXPECT_EQ(test_system_tray_client_->show_multi_device_setup_count(), 0);
   AssertNewChromebookBucketCount("MultiDeviceSetup_NotificationDismissed", 1);
 
   ShowExistingUserNewChromebookNotification();
@@ -515,7 +486,7 @@
   DismissNotification(false /* by_user */);
   VerifyNoNotificationIsVisible();
 
-  EXPECT_EQ(test_open_ui_delegate_->open_multi_device_setup_ui_count(), 0);
+  EXPECT_EQ(test_system_tray_client_->show_multi_device_setup_count(), 0);
   AssertNewChromebookBucketCount("MultiDeviceSetup_NotificationDismissed", 1);
 }
 
diff --git a/ash/public/cpp/BUILD.gn b/ash/public/cpp/BUILD.gn
index ecf5389..cd58ff410 100644
--- a/ash/public/cpp/BUILD.gn
+++ b/ash/public/cpp/BUILD.gn
@@ -297,6 +297,8 @@
     "test/test_keyboard_controller_observer.h",
     "test/test_new_window_delegate.cc",
     "test/test_new_window_delegate.h",
+    "test/test_system_tray_client.cc",
+    "test/test_system_tray_client.h",
   ]
 
   deps = [
diff --git a/ash/public/cpp/system_tray_client.h b/ash/public/cpp/system_tray_client.h
index 9d2bda3..81692e1 100644
--- a/ash/public/cpp/system_tray_client.h
+++ b/ash/public/cpp/system_tray_client.h
@@ -12,8 +12,6 @@
 
 namespace ash {
 
-class SystemTrayClient;
-
 // Handles method calls delegated back to chrome from ash.
 class ASH_PUBLIC_EXPORT SystemTrayClient {
  public:
diff --git a/ash/public/cpp/test/test_system_tray_client.cc b/ash/public/cpp/test/test_system_tray_client.cc
new file mode 100644
index 0000000..e6d941d
--- /dev/null
+++ b/ash/public/cpp/test/test_system_tray_client.cc
@@ -0,0 +1,78 @@
+// 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 "ash/public/cpp/test/test_system_tray_client.h"
+
+namespace ash {
+
+TestSystemTrayClient::TestSystemTrayClient() = default;
+
+TestSystemTrayClient::~TestSystemTrayClient() = default;
+
+void TestSystemTrayClient::ShowSettings() {}
+
+void TestSystemTrayClient::ShowBluetoothSettings() {
+  show_bluetooth_settings_count_++;
+}
+
+void TestSystemTrayClient::ShowBluetoothPairingDialog(
+    const std::string& address,
+    const base::string16& name_for_display,
+    bool paired,
+    bool connected) {}
+
+void TestSystemTrayClient::ShowDateSettings() {}
+
+void TestSystemTrayClient::ShowSetTimeDialog() {}
+
+void TestSystemTrayClient::ShowDisplaySettings() {}
+
+void TestSystemTrayClient::ShowPowerSettings() {}
+
+void TestSystemTrayClient::ShowChromeSlow() {}
+
+void TestSystemTrayClient::ShowIMESettings() {}
+
+void TestSystemTrayClient::ShowConnectedDevicesSettings() {
+  show_connected_devices_settings_count_++;
+}
+
+void TestSystemTrayClient::ShowAboutChromeOS() {}
+
+void TestSystemTrayClient::ShowHelp() {}
+
+void TestSystemTrayClient::ShowAccessibilityHelp() {}
+
+void TestSystemTrayClient::ShowAccessibilitySettings() {}
+
+void TestSystemTrayClient::ShowPaletteHelp() {}
+
+void TestSystemTrayClient::ShowPaletteSettings() {}
+
+void TestSystemTrayClient::ShowPublicAccountInfo() {}
+
+void TestSystemTrayClient::ShowEnterpriseInfo() {}
+
+void TestSystemTrayClient::ShowNetworkConfigure(const std::string& network_id) {
+}
+
+void TestSystemTrayClient::ShowNetworkCreate(const std::string& type) {}
+
+void TestSystemTrayClient::ShowThirdPartyVpnCreate(
+    const std::string& extension_id) {}
+
+void TestSystemTrayClient::ShowArcVpnCreate(const std::string& app_id) {}
+
+void TestSystemTrayClient::ShowNetworkSettings(const std::string& network_id) {}
+
+void TestSystemTrayClient::ShowMultiDeviceSetup() {
+  show_multi_device_setup_count_++;
+}
+
+void TestSystemTrayClient::RequestRestartForUpdate() {}
+
+void TestSystemTrayClient::SetLocaleAndExit(
+    const std::string& locale_iso_code) {}
+
+}  // namespace ash
diff --git a/ash/public/cpp/test/test_system_tray_client.h b/ash/public/cpp/test/test_system_tray_client.h
new file mode 100644
index 0000000..6c1b9e2
--- /dev/null
+++ b/ash/public/cpp/test/test_system_tray_client.h
@@ -0,0 +1,71 @@
+// 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 ASH_PUBLIC_CPP_TEST_TEST_SYSTEM_TRAY_CLIENT_H_
+#define ASH_PUBLIC_CPP_TEST_TEST_SYSTEM_TRAY_CLIENT_H_
+
+#include "ash/public/cpp/ash_public_export.h"
+#include "ash/public/cpp/system_tray_client.h"
+#include "base/macros.h"
+
+namespace ash {
+
+// A SystemTrayClient that does nothing. Used by AshTestBase.
+class ASH_PUBLIC_EXPORT TestSystemTrayClient : public SystemTrayClient {
+ public:
+  TestSystemTrayClient();
+  ~TestSystemTrayClient() override;
+
+  // SystemTrayClient:
+  void ShowSettings() override;
+  void ShowBluetoothSettings() override;
+  void ShowBluetoothPairingDialog(const std::string& address,
+                                  const base::string16& name_for_display,
+                                  bool paired,
+                                  bool connected) override;
+  void ShowDateSettings() override;
+  void ShowSetTimeDialog() override;
+  void ShowDisplaySettings() override;
+  void ShowPowerSettings() override;
+  void ShowChromeSlow() override;
+  void ShowIMESettings() override;
+  void ShowConnectedDevicesSettings() override;
+  void ShowAboutChromeOS() override;
+  void ShowHelp() override;
+  void ShowAccessibilityHelp() override;
+  void ShowAccessibilitySettings() override;
+  void ShowPaletteHelp() override;
+  void ShowPaletteSettings() override;
+  void ShowPublicAccountInfo() override;
+  void ShowEnterpriseInfo() override;
+  void ShowNetworkConfigure(const std::string& network_id) override;
+  void ShowNetworkCreate(const std::string& type) override;
+  void ShowThirdPartyVpnCreate(const std::string& extension_id) override;
+  void ShowArcVpnCreate(const std::string& app_id) override;
+  void ShowNetworkSettings(const std::string& network_id) override;
+  void ShowMultiDeviceSetup() override;
+  void RequestRestartForUpdate() override;
+  void SetLocaleAndExit(const std::string& locale_iso_code) override;
+
+  int show_bluetooth_settings_count() const {
+    return show_bluetooth_settings_count_;
+  }
+  int show_multi_device_setup_count() const {
+    return show_multi_device_setup_count_;
+  }
+  int show_connected_devices_settings_count() const {
+    return show_connected_devices_settings_count_;
+  }
+
+ private:
+  int show_bluetooth_settings_count_ = 0;
+  int show_multi_device_setup_count_ = 0;
+  int show_connected_devices_settings_count_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(TestSystemTrayClient);
+};
+
+}  // namespace ash
+
+#endif  // ASH_PUBLIC_CPP_TEST_TEST_SYSTEM_TRAY_CLIENT_H_
diff --git a/ash/system/bluetooth/bluetooth_notification_controller.cc b/ash/system/bluetooth/bluetooth_notification_controller.cc
index f753723..d9160b2 100644
--- a/ash/system/bluetooth/bluetooth_notification_controller.cc
+++ b/ash/system/bluetooth/bluetooth_notification_controller.cc
@@ -137,36 +137,25 @@
 class BluetoothNotificationController::BluetoothPairedNotificationDelegate
     : public message_center::NotificationDelegate {
  public:
-  explicit BluetoothPairedNotificationDelegate(OpenUiDelegate* open_delegate)
-      : open_delegate_(open_delegate) {}
+  BluetoothPairedNotificationDelegate() = default;
 
  protected:
   ~BluetoothPairedNotificationDelegate() override = default;
 
+  // message_center::NotificationDelegate:
   void Click(const base::Optional<int>& button_index,
-             const base::Optional<base::string16>& reply) override;
+             const base::Optional<base::string16>& reply) override {
+    if (TrayPopupUtils::CanOpenWebUISettings())
+      Shell::Get()->system_tray_model()->client()->ShowBluetoothSettings();
+  }
 
  private:
-  OpenUiDelegate* open_delegate_;
   DISALLOW_COPY_AND_ASSIGN(BluetoothPairedNotificationDelegate);
 };
 
-void BluetoothNotificationController::BluetoothPairedNotificationDelegate::
-    Click(const base::Optional<int>& button_index,
-          const base::Optional<base::string16>& reply) {
-  if (TrayPopupUtils::CanOpenWebUISettings())
-    open_delegate_->OpenBluetoothSettings();
-}
-
-void BluetoothNotificationController::OpenUiDelegate::OpenBluetoothSettings() {
-  Shell::Get()->system_tray_model()->client()->ShowBluetoothSettings();
-}
-
 BluetoothNotificationController::BluetoothNotificationController(
     message_center::MessageCenter* message_center)
-    : open_delegate_(std::make_unique<OpenUiDelegate>()),
-      message_center_(message_center),
-      weak_ptr_factory_(this) {
+    : message_center_(message_center), weak_ptr_factory_(this) {
   BluetoothAdapterFactory::GetAdapter(
       base::BindOnce(&BluetoothNotificationController::OnGetAdapter,
                      weak_ptr_factory_.GetWeakPtr()));
@@ -365,8 +354,7 @@
       message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
                                  kNotifierBluetooth),
       message_center::RichNotificationData(),
-      base::MakeRefCounted<BluetoothPairedNotificationDelegate>(
-          open_delegate_.get()),
+      base::MakeRefCounted<BluetoothPairedNotificationDelegate>(),
       kNotificationBluetoothIcon,
       message_center::SystemNotificationWarningLevel::NORMAL);
   message_center_->AddNotification(std::move(notification));
diff --git a/ash/system/bluetooth/bluetooth_notification_controller.h b/ash/system/bluetooth/bluetooth_notification_controller.h
index 9a1aac9f..81accf4d 100644
--- a/ash/system/bluetooth/bluetooth_notification_controller.h
+++ b/ash/system/bluetooth/bluetooth_notification_controller.h
@@ -62,14 +62,6 @@
   friend class BluetoothNotificationControllerTest;
   class BluetoothPairedNotificationDelegate;
 
-  // Wraps calls to settings code which are mocked out for tests.
-  class OpenUiDelegate {
-   public:
-    OpenUiDelegate() = default;
-    virtual ~OpenUiDelegate() = default;
-    virtual void OpenBluetoothSettings();
-  };
-
   static const char kBluetoothDeviceDiscoverableNotificationId[];
   // Identifier for the pairing notification; the Bluetooth code ensures we
   // only receive one pairing request at a time, so a single id is sufficient
@@ -98,8 +90,6 @@
   // Clears any shown pairing notification now that the device has been paired.
   void NotifyPairedDevice(device::BluetoothDevice* device);
 
-  std::unique_ptr<OpenUiDelegate> open_delegate_;
-
   message_center::MessageCenter* const message_center_;
 
   // Reference to the underlying BluetoothAdapter object, holding this reference
diff --git a/ash/system/bluetooth/bluetooth_notification_controller_unittest.cc b/ash/system/bluetooth/bluetooth_notification_controller_unittest.cc
index 180c7f2..5f83047 100644
--- a/ash/system/bluetooth/bluetooth_notification_controller_unittest.cc
+++ b/ash/system/bluetooth/bluetooth_notification_controller_unittest.cc
@@ -7,11 +7,11 @@
 #include <memory>
 #include <utility>
 
+#include "ash/public/cpp/test/test_system_tray_client.h"
 #include "ash/session/test_session_controller_client.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/system/tray/tray_popup_utils.h"
 #include "ash/test/ash_test_base.h"
-#include "ash/test/ash_test_helper.h"
 #include "base/containers/flat_map.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
@@ -47,27 +47,6 @@
 
 class BluetoothNotificationControllerTest : public AshTestBase {
  public:
-  class TestOpenUiDelegate
-      : public BluetoothNotificationController::OpenUiDelegate {
-   public:
-    TestOpenUiDelegate() = default;
-    ~TestOpenUiDelegate() override = default;
-
-    size_t open_bluetooth_settings_ui_count() const {
-      return open_bluetooth_settings_ui_count_;
-    }
-
-    void OpenBluetoothSettings() override {
-      ++open_bluetooth_settings_ui_count_;
-    }
-
-   private:
-    size_t open_bluetooth_settings_ui_count_ = 0u;
-
-    DISALLOW_COPY_AND_ASSIGN(TestOpenUiDelegate);
-  };
-
- protected:
   BluetoothNotificationControllerTest() = default;
 
   void SetUp() override {
@@ -75,9 +54,7 @@
     notification_controller_ =
         std::make_unique<BluetoothNotificationController>(
             &test_message_center_);
-    auto open_delegate = std::make_unique<TestOpenUiDelegate>();
-    open_ui_delegate_ = open_delegate.get();
-    notification_controller_->open_delegate_ = std::move(open_delegate);
+    system_tray_client_ = GetSystemTrayClient();
     bluetooth_device_ =
         std::make_unique<testing::NiceMock<device::MockBluetoothDevice>>(
             nullptr /* adapter */, 0 /* bluetooth_class */, "name", "address",
@@ -126,7 +103,7 @@
 
   TestMessageCenter test_message_center_;
   std::unique_ptr<BluetoothNotificationController> notification_controller_;
-  TestOpenUiDelegate* open_ui_delegate_;
+  TestSystemTrayClient* system_tray_client_;
   std::unique_ptr<device::MockBluetoothDevice> bluetooth_device_;
 
   DISALLOW_COPY_AND_ASSIGN(BluetoothNotificationControllerTest);
@@ -146,7 +123,7 @@
   VerifyPairedNotificationIsVisible(bluetooth_device_.get());
 
   // Check the notification controller tried to open the UI.
-  EXPECT_EQ(1u, open_ui_delegate_->open_bluetooth_settings_ui_count());
+  EXPECT_EQ(1, system_tray_client_->show_bluetooth_settings_count());
 }
 
 TEST_F(BluetoothNotificationControllerTest,
@@ -160,7 +137,7 @@
 
   VerifyPairedNotificationIsNotVisible();
   // The settings UI should not open when closing the notification.
-  EXPECT_EQ(0u, open_ui_delegate_->open_bluetooth_settings_ui_count());
+  EXPECT_EQ(0, system_tray_client_->show_bluetooth_settings_count());
 }
 
 TEST_F(BluetoothNotificationControllerTest,
@@ -173,7 +150,7 @@
   DismissPairedNotification(false /* by_user */);
 
   VerifyPairedNotificationIsNotVisible();
-  EXPECT_EQ(0u, open_ui_delegate_->open_bluetooth_settings_ui_count());
+  EXPECT_EQ(0, system_tray_client_->show_bluetooth_settings_count());
 }
 
 }  // namespace ash
diff --git a/ash/test/ash_test_base.cc b/ash/test/ash_test_base.cc
index 5e3b9b7..d606c44 100644
--- a/ash/test/ash_test_base.cc
+++ b/ash/test/ash_test_base.cc
@@ -361,6 +361,10 @@
   return ash_test_helper_->test_session_controller_client();
 }
 
+TestSystemTrayClient* AshTestBase::GetSystemTrayClient() {
+  return ash_test_helper_->system_tray_client();
+}
+
 AppListTestHelper* AshTestBase::GetAppListTestHelper() {
   return ash_test_helper_->app_list_test_helper();
 }
diff --git a/ash/test/ash_test_base.h b/ash/test/ash_test_base.h
index da1a195a1..1a9a676 100644
--- a/ash/test/ash_test_base.h
+++ b/ash/test/ash_test_base.h
@@ -67,6 +67,7 @@
 class AshTestHelper;
 class Shelf;
 class TestScreenshotDelegate;
+class TestSystemTrayClient;
 class UnifiedSystemTray;
 class WorkAreaInsets;
 
@@ -194,6 +195,8 @@
 
   TestSessionControllerClient* GetSessionControllerClient();
 
+  TestSystemTrayClient* GetSystemTrayClient();
+
   AppListTestHelper* GetAppListTestHelper();
 
   // Emulates an ash session that have |session_count| user sessions running.
diff --git a/ash/test/ash_test_helper.cc b/ash/test/ash_test_helper.cc
index c8c0c10..64ca13e 100644
--- a/ash/test/ash_test_helper.cc
+++ b/ash/test/ash_test_helper.cc
@@ -19,10 +19,12 @@
 #include "ash/public/cpp/ash_switches.h"
 #include "ash/public/cpp/test/test_keyboard_controller_observer.h"
 #include "ash/public/cpp/test/test_new_window_delegate.h"
+#include "ash/public/cpp/test/test_system_tray_client.h"
 #include "ash/session/test_pref_service_provider.h"
 #include "ash/session/test_session_controller_client.h"
 #include "ash/shell.h"
 #include "ash/shell_init_params.h"
+#include "ash/system/model/system_tray_model.h"
 #include "ash/system/screen_layout_observer.h"
 #include "ash/test/ash_test_views_delegate.h"
 #include "ash/test_shell_delegate.h"
@@ -151,6 +153,9 @@
       shell->session_controller(), prefs_provider_.get()));
   session_controller_client_->InitializeAndSetClient();
 
+  system_tray_client_ = std::make_unique<TestSystemTrayClient>();
+  shell->system_tray_model()->SetClient(system_tray_client_.get());
+
   if (start_session)
     session_controller_client_->CreatePredefinedUserSessions(1);
 
diff --git a/ash/test/ash_test_helper.h b/ash/test/ash_test_helper.h
index be17ffa..aac29cd 100644
--- a/ash/test/ash_test_helper.h
+++ b/ash/test/ash_test_helper.h
@@ -47,6 +47,7 @@
 class TestNewWindowDelegate;
 class TestPrefServiceProvider;
 class TestShellDelegate;
+class TestSystemTrayClient;
 
 // A helper class that does common initialization required for Ash. Creates a
 // root window and an ash::Shell instance with a test delegate.
@@ -88,6 +89,9 @@
       std::unique_ptr<TestSessionControllerClient> session_controller_client) {
     session_controller_client_ = std::move(session_controller_client);
   }
+  TestSystemTrayClient* system_tray_client() {
+    return system_tray_client_.get();
+  }
   TestPrefServiceProvider* prefs_provider() { return prefs_provider_.get(); }
 
   AppListTestHelper* app_list_test_helper() {
@@ -118,6 +122,7 @@
   bool power_policy_controller_initialized_ = false;
 
   std::unique_ptr<TestSessionControllerClient> session_controller_client_;
+  std::unique_ptr<TestSystemTrayClient> system_tray_client_;
   std::unique_ptr<TestPrefServiceProvider> prefs_provider_;
 
   std::unique_ptr<ui::TestContextFactories> context_factories_;
diff --git a/base/command_line.cc b/base/command_line.cc
index 3f13db20..1451251 100644
--- a/base/command_line.cc
+++ b/base/command_line.cc
@@ -286,7 +286,7 @@
 
 bool CommandLine::HasSwitch(const StringPiece& switch_string) const {
   DCHECK_EQ(ToLowerASCII(switch_string), switch_string);
-  return ContainsKey(switches_, switch_string);
+  return Contains(switches_, switch_string);
 }
 
 bool CommandLine::HasSwitch(const char switch_constant[]) const {
diff --git a/base/cpu_unittest.cc b/base/cpu_unittest.cc
index 8a68ea0..b6403af 100644
--- a/base/cpu_unittest.cc
+++ b/base/cpu_unittest.cc
@@ -129,6 +129,6 @@
 // For https://crbug.com/249713
 TEST(CPU, BrandAndVendorContainsNoNUL) {
   base::CPU cpu;
-  EXPECT_FALSE(base::ContainsValue(cpu.cpu_brand(), '\0'));
-  EXPECT_FALSE(base::ContainsValue(cpu.vendor_name(), '\0'));
+  EXPECT_FALSE(base::Contains(cpu.cpu_brand(), '\0'));
+  EXPECT_FALSE(base::Contains(cpu.vendor_name(), '\0'));
 }
diff --git a/base/debug/activity_analyzer.cc b/base/debug/activity_analyzer.cc
index 1bd60262..d84dd0e 100644
--- a/base/debug/activity_analyzer.cc
+++ b/base/debug/activity_analyzer.cc
@@ -354,7 +354,7 @@
         // Add this analyzer to the map of known ones, indexed by a unique
         // thread
         // identifier.
-        DCHECK(!base::ContainsKey(analyzers_, analyzer->GetThreadKey()));
+        DCHECK(!base::Contains(analyzers_, analyzer->GetThreadKey()));
         analyzer->allocator_reference_ = ref;
         analyzers_[analyzer->GetThreadKey()] = std::move(analyzer);
       } break;
@@ -364,7 +364,7 @@
         int64_t process_id;
         int64_t create_stamp;
         ActivityUserData::GetOwningProcessId(base, &process_id, &create_stamp);
-        DCHECK(!base::ContainsKey(process_data_, process_id));
+        DCHECK(!base::Contains(process_data_, process_id));
 
         // Create a snapshot of the data. This can fail if the data is somehow
         // corrupted or the process shutdown and the memory being released.
diff --git a/base/debug/activity_analyzer_unittest.cc b/base/debug/activity_analyzer_unittest.cc
index 15b08f9..4fdc2d27 100644
--- a/base/debug/activity_analyzer_unittest.cc
+++ b/base/debug/activity_analyzer_unittest.cc
@@ -294,22 +294,22 @@
       const ActivityUserData::Snapshot& user_data =
           analyzer_snapshot.user_data_stack.at(1);
       EXPECT_EQ(8U, user_data.size());
-      ASSERT_TRUE(ContainsKey(user_data, "raw2"));
+      ASSERT_TRUE(Contains(user_data, "raw2"));
       EXPECT_EQ("foo2", user_data.at("raw2").Get().as_string());
-      ASSERT_TRUE(ContainsKey(user_data, "string2"));
+      ASSERT_TRUE(Contains(user_data, "string2"));
       EXPECT_EQ("bar2", user_data.at("string2").GetString().as_string());
-      ASSERT_TRUE(ContainsKey(user_data, "char2"));
+      ASSERT_TRUE(Contains(user_data, "char2"));
       EXPECT_EQ('2', user_data.at("char2").GetChar());
-      ASSERT_TRUE(ContainsKey(user_data, "int2"));
+      ASSERT_TRUE(Contains(user_data, "int2"));
       EXPECT_EQ(-2222, user_data.at("int2").GetInt());
-      ASSERT_TRUE(ContainsKey(user_data, "uint2"));
+      ASSERT_TRUE(Contains(user_data, "uint2"));
       EXPECT_EQ(2222U, user_data.at("uint2").GetUint());
-      ASSERT_TRUE(ContainsKey(user_data, "bool2"));
+      ASSERT_TRUE(Contains(user_data, "bool2"));
       EXPECT_FALSE(user_data.at("bool2").GetBool());
-      ASSERT_TRUE(ContainsKey(user_data, "ref2"));
+      ASSERT_TRUE(Contains(user_data, "ref2"));
       EXPECT_EQ(string2a, user_data.at("ref2").GetReference().data());
       EXPECT_EQ(sizeof(string2a), user_data.at("ref2").GetReference().size());
-      ASSERT_TRUE(ContainsKey(user_data, "sref2"));
+      ASSERT_TRUE(Contains(user_data, "sref2"));
       EXPECT_EQ(string2b, user_data.at("sref2").GetStringReference().data());
       EXPECT_EQ(strlen(string2b),
                 user_data.at("sref2").GetStringReference().size());
@@ -372,22 +372,22 @@
   DCHECK_EQ(pid, first_pid);
   const ActivityUserData::Snapshot& snapshot =
       global_analyzer.GetProcessDataSnapshot(pid);
-  ASSERT_TRUE(ContainsKey(snapshot, "raw"));
+  ASSERT_TRUE(Contains(snapshot, "raw"));
   EXPECT_EQ("foo", snapshot.at("raw").Get().as_string());
-  ASSERT_TRUE(ContainsKey(snapshot, "string"));
+  ASSERT_TRUE(Contains(snapshot, "string"));
   EXPECT_EQ("bar", snapshot.at("string").GetString().as_string());
-  ASSERT_TRUE(ContainsKey(snapshot, "char"));
+  ASSERT_TRUE(Contains(snapshot, "char"));
   EXPECT_EQ('9', snapshot.at("char").GetChar());
-  ASSERT_TRUE(ContainsKey(snapshot, "int"));
+  ASSERT_TRUE(Contains(snapshot, "int"));
   EXPECT_EQ(-9999, snapshot.at("int").GetInt());
-  ASSERT_TRUE(ContainsKey(snapshot, "uint"));
+  ASSERT_TRUE(Contains(snapshot, "uint"));
   EXPECT_EQ(9999U, snapshot.at("uint").GetUint());
-  ASSERT_TRUE(ContainsKey(snapshot, "bool"));
+  ASSERT_TRUE(Contains(snapshot, "bool"));
   EXPECT_TRUE(snapshot.at("bool").GetBool());
-  ASSERT_TRUE(ContainsKey(snapshot, "ref"));
+  ASSERT_TRUE(Contains(snapshot, "ref"));
   EXPECT_EQ(string1, snapshot.at("ref").GetReference().data());
   EXPECT_EQ(sizeof(string1), snapshot.at("ref").GetReference().size());
-  ASSERT_TRUE(ContainsKey(snapshot, "sref"));
+  ASSERT_TRUE(Contains(snapshot, "sref"));
   EXPECT_EQ(string2, snapshot.at("sref").GetStringReference().data());
   EXPECT_EQ(strlen(string2), snapshot.at("sref").GetStringReference().size());
 }
diff --git a/base/debug/activity_tracker.cc b/base/debug/activity_tracker.cc
index f5ee9c48..fbf49c7 100644
--- a/base/debug/activity_tracker.cc
+++ b/base/debug/activity_tracker.cc
@@ -1427,7 +1427,7 @@
   DCHECK_NE(0, pid);
 
   base::AutoLock lock(global_tracker_lock_);
-  if (base::ContainsKey(known_processes_, pid)) {
+  if (base::Contains(known_processes_, pid)) {
     // TODO(bcwhite): Measure this in UMA.
     NOTREACHED() << "Process #" << process_id
                  << " was previously recorded as \"launched\""
diff --git a/base/debug/debugger_posix.cc b/base/debug/debugger_posix.cc
index 674a78b..45bf1da 100644
--- a/base/debug/debugger_posix.cc
+++ b/base/debug/debugger_posix.cc
@@ -19,7 +19,6 @@
 
 #include "base/clang_coverage_buildflags.h"
 #include "base/stl_util.h"
-#include "base/test/clang_coverage.h"
 #include "base/threading/platform_thread.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
@@ -52,6 +51,10 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_piece.h"
 
+#if BUILDFLAG(CLANG_COVERAGE)
+#include "base/test/clang_coverage.h"
+#endif
+
 #if defined(USE_SYMBOLIZE)
 #include "base/third_party/symbolize/symbolize.h"
 #endif
diff --git a/base/debug/debugger_win.cc b/base/debug/debugger_win.cc
index 53dfcd2..fd10b4b 100644
--- a/base/debug/debugger_win.cc
+++ b/base/debug/debugger_win.cc
@@ -8,7 +8,10 @@
 #include <windows.h>
 
 #include "base/clang_coverage_buildflags.h"
+
+#if BUILDFLAG(CLANG_COVERAGE)
 #include "base/test/clang_coverage.h"
+#endif
 
 namespace base {
 namespace debug {
diff --git a/base/feature_list.cc b/base/feature_list.cc
index a92ec44..56aa03c 100644
--- a/base/feature_list.cc
+++ b/base/feature_list.cc
@@ -150,7 +150,7 @@
                                              OverrideState override_state,
                                              FieldTrial* field_trial) {
   DCHECK(field_trial);
-  DCHECK(!ContainsKey(overrides_, feature_name) ||
+  DCHECK(!Contains(overrides_, feature_name) ||
          !overrides_.find(feature_name)->second.field_trial)
       << "Feature " << feature_name
       << " has conflicting field trial overrides: "
diff --git a/base/files/file_path_watcher_linux.cc b/base/files/file_path_watcher_linux.cc
index 0ff8924..266d66b 100644
--- a/base/files/file_path_watcher_linux.cc
+++ b/base/files/file_path_watcher_linux.cc
@@ -496,7 +496,7 @@
     }
   }
 
-  if (ContainsKey(recursive_paths_by_watch_, fired_watch)) {
+  if (Contains(recursive_paths_by_watch_, fired_watch)) {
     if (!did_update)
       UpdateRecursiveWatches(fired_watch, is_dir);
     callback_.Run(target_, false /* error */);
@@ -607,7 +607,7 @@
 
   // Check to see if this is a forced update or if some component of |target_|
   // has changed. For these cases, redo the watches for |target_| and below.
-  if (!ContainsKey(recursive_paths_by_watch_, fired_watch) &&
+  if (!Contains(recursive_paths_by_watch_, fired_watch) &&
       fired_watch != watches_.back().watch) {
     UpdateRecursiveWatchesForPath(target_);
     return;
@@ -617,10 +617,9 @@
   if (!is_dir)
     return;
 
-  const FilePath& changed_dir =
-      ContainsKey(recursive_paths_by_watch_, fired_watch) ?
-      recursive_paths_by_watch_[fired_watch] :
-      target_;
+  const FilePath& changed_dir = Contains(recursive_paths_by_watch_, fired_watch)
+                                    ? recursive_paths_by_watch_[fired_watch]
+                                    : target_;
 
   auto start_it = recursive_watches_by_path_.lower_bound(changed_dir);
   auto end_it = start_it;
@@ -652,7 +651,7 @@
        current = enumerator.Next()) {
     DCHECK(enumerator.GetInfo().IsDirectory());
 
-    if (!ContainsKey(recursive_watches_by_path_, current)) {
+    if (!Contains(recursive_watches_by_path_, current)) {
       // Add new watches.
       InotifyReader::Watch watch =
           g_inotify_reader.Get().AddWatch(current, this);
@@ -686,8 +685,8 @@
   if (watch == InotifyReader::kInvalidWatch)
     return;
 
-  DCHECK(!ContainsKey(recursive_paths_by_watch_, watch));
-  DCHECK(!ContainsKey(recursive_watches_by_path_, path));
+  DCHECK(!Contains(recursive_paths_by_watch_, watch));
+  DCHECK(!Contains(recursive_watches_by_path_, path));
   recursive_paths_by_watch_[watch] = path;
   recursive_watches_by_path_[path] = watch;
 }
diff --git a/base/files/file_util_posix.cc b/base/files/file_util_posix.cc
index c05a09b..11bf492 100644
--- a/base/files/file_util_posix.cc
+++ b/base/files/file_util_posix.cc
@@ -114,7 +114,7 @@
   }
 
   if ((stat_info.st_mode & S_IWGRP) &&
-      !ContainsKey(group_gids, stat_info.st_gid)) {
+      !Contains(group_gids, stat_info.st_gid)) {
     DLOG(ERROR) << "Path " << path.value()
                 << " is writable by an unprivileged group.";
     return false;
diff --git a/base/ios/crb_protocol_observers.mm b/base/ios/crb_protocol_observers.mm
index 1a3b9f73..86a081e 100644
--- a/base/ios/crb_protocol_observers.mm
+++ b/base/ios/crb_protocol_observers.mm
@@ -104,7 +104,7 @@
   DCHECK(observer);
   DCHECK([observer conformsToProtocol:self.protocol]);
 
-  if (base::ContainsValue(_observers, observer))
+  if (base::Contains(_observers, observer))
     return;
 
   _observers.push_back(observer);
diff --git a/base/mac/objc_release_properties_unittest.mm b/base/mac/objc_release_properties_unittest.mm
index 5f15650..4b51e42 100644
--- a/base/mac/objc_release_properties_unittest.mm
+++ b/base/mac/objc_release_properties_unittest.mm
@@ -217,7 +217,7 @@
     @selector(baseCvcDynamic), @selector(derivedCvcDynamic),
         @selector(protoCvcDynamic),
   };
-  if (!base::ContainsValue(dynamicMethods, sel)) {
+  if (!base::Contains(dynamicMethods, sel)) {
     return NO;
   }
   id (*imp)() = []() -> id { return nil; };
diff --git a/base/memory/shared_memory_mapping.cc b/base/memory/shared_memory_mapping.cc
index 2be25700..8426fa8 100644
--- a/base/memory/shared_memory_mapping.cc
+++ b/base/memory/shared_memory_mapping.cc
@@ -33,7 +33,7 @@
 
 SharedMemoryMapping::SharedMemoryMapping() = default;
 
-SharedMemoryMapping::SharedMemoryMapping(SharedMemoryMapping&& mapping)
+SharedMemoryMapping::SharedMemoryMapping(SharedMemoryMapping&& mapping) noexcept
     : memory_(mapping.memory_),
       size_(mapping.size_),
       mapped_size_(mapping.mapped_size_),
@@ -42,7 +42,7 @@
 }
 
 SharedMemoryMapping& SharedMemoryMapping::operator=(
-    SharedMemoryMapping&& mapping) {
+    SharedMemoryMapping&& mapping) noexcept {
   Unmap();
   memory_ = mapping.memory_;
   size_ = mapping.size_;
@@ -90,9 +90,9 @@
 
 ReadOnlySharedMemoryMapping::ReadOnlySharedMemoryMapping() = default;
 ReadOnlySharedMemoryMapping::ReadOnlySharedMemoryMapping(
-    ReadOnlySharedMemoryMapping&&) = default;
+    ReadOnlySharedMemoryMapping&&) noexcept = default;
 ReadOnlySharedMemoryMapping& ReadOnlySharedMemoryMapping::operator=(
-    ReadOnlySharedMemoryMapping&&) = default;
+    ReadOnlySharedMemoryMapping&&) noexcept = default;
 ReadOnlySharedMemoryMapping::ReadOnlySharedMemoryMapping(
     void* address,
     size_t size,
@@ -102,9 +102,9 @@
 
 WritableSharedMemoryMapping::WritableSharedMemoryMapping() = default;
 WritableSharedMemoryMapping::WritableSharedMemoryMapping(
-    WritableSharedMemoryMapping&&) = default;
+    WritableSharedMemoryMapping&&) noexcept = default;
 WritableSharedMemoryMapping& WritableSharedMemoryMapping::operator=(
-    WritableSharedMemoryMapping&&) = default;
+    WritableSharedMemoryMapping&&) noexcept = default;
 WritableSharedMemoryMapping::WritableSharedMemoryMapping(
     void* address,
     size_t size,
diff --git a/base/memory/shared_memory_mapping.h b/base/memory/shared_memory_mapping.h
index d9569af..2b8858e 100644
--- a/base/memory/shared_memory_mapping.h
+++ b/base/memory/shared_memory_mapping.h
@@ -32,8 +32,8 @@
   SharedMemoryMapping();
 
   // Move operations are allowed.
-  SharedMemoryMapping(SharedMemoryMapping&& mapping);
-  SharedMemoryMapping& operator=(SharedMemoryMapping&& mapping);
+  SharedMemoryMapping(SharedMemoryMapping&& mapping) noexcept;
+  SharedMemoryMapping& operator=(SharedMemoryMapping&& mapping) noexcept;
 
   // Unmaps the region if the mapping is valid.
   virtual ~SharedMemoryMapping();
@@ -93,8 +93,9 @@
   ReadOnlySharedMemoryMapping();
 
   // Move operations are allowed.
-  ReadOnlySharedMemoryMapping(ReadOnlySharedMemoryMapping&&);
-  ReadOnlySharedMemoryMapping& operator=(ReadOnlySharedMemoryMapping&&);
+  ReadOnlySharedMemoryMapping(ReadOnlySharedMemoryMapping&&) noexcept;
+  ReadOnlySharedMemoryMapping& operator=(
+      ReadOnlySharedMemoryMapping&&) noexcept;
 
   // Returns the base address of the mapping. This is read-only memory. This is
   // page-aligned. This is nullptr for invalid instances.
@@ -171,8 +172,9 @@
   WritableSharedMemoryMapping();
 
   // Move operations are allowed.
-  WritableSharedMemoryMapping(WritableSharedMemoryMapping&&);
-  WritableSharedMemoryMapping& operator=(WritableSharedMemoryMapping&&);
+  WritableSharedMemoryMapping(WritableSharedMemoryMapping&&) noexcept;
+  WritableSharedMemoryMapping& operator=(
+      WritableSharedMemoryMapping&&) noexcept;
 
   // Returns the base address of the mapping. This is writable memory. This is
   // page-aligned. This is nullptr for invalid instances.
diff --git a/base/metrics/field_trial.cc b/base/metrics/field_trial.cc
index 94e663f..9236f44 100644
--- a/base/metrics/field_trial.cc
+++ b/base/metrics/field_trial.cc
@@ -733,7 +733,7 @@
     const std::string trial_name = entry.trial_name.as_string();
     const std::string group_name = entry.group_name.as_string();
 
-    if (ContainsKey(ignored_trial_names, trial_name)) {
+    if (Contains(ignored_trial_names, trial_name)) {
       // This is to warn that the field trial forced through command-line
       // input is unforcable.
       // Use --enable-logging or --enable-logging=stderr to see this warning.
diff --git a/base/metrics/field_trial_param_associator.cc b/base/metrics/field_trial_param_associator.cc
index af76eaf..6360b8c 100644
--- a/base/metrics/field_trial_param_associator.cc
+++ b/base/metrics/field_trial_param_associator.cc
@@ -26,7 +26,7 @@
 
   AutoLock scoped_lock(lock_);
   const FieldTrialKey key(trial_name, group_name);
-  if (ContainsKey(field_trial_params_, key))
+  if (Contains(field_trial_params_, key))
     return false;
 
   field_trial_params_[key] = params;
@@ -57,7 +57,7 @@
   AutoLock scoped_lock(lock_);
 
   const FieldTrialKey key(trial_name, group_name);
-  if (!ContainsKey(field_trial_params_, key))
+  if (!Contains(field_trial_params_, key))
     return false;
 
   *params = field_trial_params_[key];
diff --git a/base/metrics/histogram_snapshot_manager_unittest.cc b/base/metrics/histogram_snapshot_manager_unittest.cc
index 1e2c599..2fbf182 100644
--- a/base/metrics/histogram_snapshot_manager_unittest.cc
+++ b/base/metrics/histogram_snapshot_manager_unittest.cc
@@ -25,8 +25,7 @@
                    const HistogramSamples& snapshot) override {
     recorded_delta_histogram_names_.push_back(histogram.histogram_name());
     // Use CHECK instead of ASSERT to get full stack-trace and thus origin.
-    CHECK(!ContainsKey(recorded_delta_histogram_sum_,
-                       histogram.histogram_name()));
+    CHECK(!Contains(recorded_delta_histogram_sum_, histogram.histogram_name()));
     // Keep pointer to snapshot for testing. This really isn't ideal but the
     // snapshot-manager keeps the snapshot alive until it's "forgotten".
     recorded_delta_histogram_sum_[histogram.histogram_name()] = snapshot.sum();
@@ -42,7 +41,7 @@
   }
 
   int64_t GetRecordedDeltaHistogramSum(const std::string& name) {
-    EXPECT_TRUE(ContainsKey(recorded_delta_histogram_sum_, name));
+    EXPECT_TRUE(Contains(recorded_delta_histogram_sum_, name));
     return recorded_delta_histogram_sum_[name];
   }
 
diff --git a/base/metrics/persistent_sample_map.cc b/base/metrics/persistent_sample_map.cc
index e07b716..ba73128b 100644
--- a/base/metrics/persistent_sample_map.cc
+++ b/base/metrics/persistent_sample_map.cc
@@ -276,7 +276,7 @@
     DCHECK_EQ(id(), record->id);
 
     // Check if the record's value is already known.
-    if (!ContainsKey(sample_counts_, record->value)) {
+    if (!Contains(sample_counts_, record->value)) {
       // No: Add it to map of known values.
       sample_counts_[record->value] = &record->count;
     } else {
diff --git a/base/observer_list_threadsafe.h b/base/observer_list_threadsafe.h
index 5036ffc..6388ba4 100644
--- a/base/observer_list_threadsafe.h
+++ b/base/observer_list_threadsafe.h
@@ -107,7 +107,7 @@
     AutoLock auto_lock(lock_);
 
     // Add |observer| to the list of observers.
-    DCHECK(!ContainsKey(observers_, observer));
+    DCHECK(!Contains(observers_, observer));
     const scoped_refptr<SequencedTaskRunner> task_runner =
         SequencedTaskRunnerHandle::Get();
     observers_[observer] = task_runner;
diff --git a/base/process/process_fuchsia.cc b/base/process/process_fuchsia.cc
index 8ecf9d61..f260575 100644
--- a/base/process/process_fuchsia.cc
+++ b/base/process/process_fuchsia.cc
@@ -13,7 +13,10 @@
 #include "base/fuchsia/default_job.h"
 #include "base/fuchsia/fuchsia_logging.h"
 #include "base/strings/stringprintf.h"
+
+#if BUILDFLAG(CLANG_COVERAGE)
 #include "base/test/clang_coverage.h"
+#endif
 
 namespace base {
 
diff --git a/base/process/process_posix.cc b/base/process/process_posix.cc
index 38ee542..9636d44 100644
--- a/base/process/process_posix.cc
+++ b/base/process/process_posix.cc
@@ -16,7 +16,6 @@
 #include "base/logging.h"
 #include "base/posix/eintr_wrapper.h"
 #include "base/process/kill.h"
-#include "base/test/clang_coverage.h"
 #include "base/threading/thread_restrictions.h"
 #include "build/build_config.h"
 
@@ -24,6 +23,10 @@
 #include <sys/event.h>
 #endif
 
+#if BUILDFLAG(CLANG_COVERAGE)
+#include "base/test/clang_coverage.h"
+#endif
+
 namespace {
 
 #if !defined(OS_NACL_NONSFI)
diff --git a/base/process/process_win.cc b/base/process/process_win.cc
index ae97a92..dd065b6 100644
--- a/base/process/process_win.cc
+++ b/base/process/process_win.cc
@@ -9,11 +9,14 @@
 #include "base/logging.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/process/kill.h"
-#include "base/test/clang_coverage.h"
 #include "base/threading/thread_restrictions.h"
 
 #include <windows.h>
 
+#if BUILDFLAG(CLANG_COVERAGE)
+#include "base/test/clang_coverage.h"
+#endif
+
 namespace {
 
 DWORD kBasicProcessAccess =
diff --git a/base/sampling_heap_profiler/sampling_heap_profiler.cc b/base/sampling_heap_profiler/sampling_heap_profiler.cc
index f8736ef..676beca 100644
--- a/base/sampling_heap_profiler/sampling_heap_profiler.cc
+++ b/base/sampling_heap_profiler/sampling_heap_profiler.cc
@@ -17,7 +17,6 @@
 #include "base/no_destructor.h"
 #include "base/partition_alloc_buildflags.h"
 #include "base/sampling_heap_profiler/lock_free_address_hash_set.h"
-#include "base/threading/thread_id_name_manager.h"
 #include "base/threading/thread_local_storage.h"
 #include "base/trace_event/heap_profiler_allocation_context_tracker.h"
 #include "build/build_config.h"
@@ -94,7 +93,10 @@
 SamplingHeapProfiler::Sample::~Sample() = default;
 
 SamplingHeapProfiler::SamplingHeapProfiler() = default;
-SamplingHeapProfiler::~SamplingHeapProfiler() = default;
+SamplingHeapProfiler::~SamplingHeapProfiler() {
+  if (record_thread_names_)
+    base::ThreadIdNameManager::GetInstance()->RemoveObserver(this);
+}
 
 uint32_t SamplingHeapProfiler::Start() {
 #if defined(OS_ANDROID) && BUILDFLAG(CAN_UNWIND_WITH_CFI_TABLE) && \
@@ -124,10 +126,13 @@
 }
 
 void SamplingHeapProfiler::SetRecordThreadNames(bool value) {
+  if (record_thread_names_ == value)
+    return;
   record_thread_names_ = value;
   if (value) {
-    base::ThreadIdNameManager::GetInstance()->InstallSetNameCallback(
-        base::BindRepeating(IgnoreResult(&UpdateAndGetThreadName)));
+    base::ThreadIdNameManager::GetInstance()->AddObserver(this);
+  } else {
+    base::ThreadIdNameManager::GetInstance()->RemoveObserver(this);
   }
 }
 
@@ -284,4 +289,8 @@
   return instance.get();
 }
 
+void SamplingHeapProfiler::OnThreadNameChanged(const char* name) {
+  UpdateAndGetThreadName(name);
+}
+
 }  // namespace base
diff --git a/base/sampling_heap_profiler/sampling_heap_profiler.h b/base/sampling_heap_profiler/sampling_heap_profiler.h
index 398bf0e9..a1dca2ec 100644
--- a/base/sampling_heap_profiler/sampling_heap_profiler.h
+++ b/base/sampling_heap_profiler/sampling_heap_profiler.h
@@ -14,6 +14,7 @@
 #include "base/macros.h"
 #include "base/sampling_heap_profiler/poisson_allocation_sampler.h"
 #include "base/synchronization/lock.h"
+#include "base/threading/thread_id_name_manager.h"
 
 namespace base {
 
@@ -25,7 +26,8 @@
 // record samples.
 // The recorded samples can then be retrieved using GetSamples method.
 class BASE_EXPORT SamplingHeapProfiler
-    : private PoissonAllocationSampler::SamplesObserver {
+    : private PoissonAllocationSampler::SamplesObserver,
+      public base::ThreadIdNameManager::Observer {
  public:
   class BASE_EXPORT Sample {
    public:
@@ -95,6 +97,9 @@
   static void Init();
   static SamplingHeapProfiler* Get();
 
+  // ThreadIdNameManager::Observer implementation:
+  void OnThreadNameChanged(const char* name) override;
+
  private:
   SamplingHeapProfiler();
   ~SamplingHeapProfiler() override;
diff --git a/base/scoped_generic_unittest.cc b/base/scoped_generic_unittest.cc
index f75adf0..73291a8 100644
--- a/base/scoped_generic_unittest.cc
+++ b/base/scoped_generic_unittest.cc
@@ -215,18 +215,18 @@
   std::unordered_set<int> freed;
   TrackedIntTraits traits(&freed, &owners);
 
-#define ASSERT_OWNED(value, owner)               \
-  ASSERT_TRUE(base::ContainsKey(owners, value)); \
-  ASSERT_EQ(&owner, owners[value]);              \
-  ASSERT_FALSE(base::ContainsKey(freed, value))
+#define ASSERT_OWNED(value, owner)            \
+  ASSERT_TRUE(base::Contains(owners, value)); \
+  ASSERT_EQ(&owner, owners[value]);           \
+  ASSERT_FALSE(base::Contains(freed, value))
 
-#define ASSERT_UNOWNED(value)                     \
-  ASSERT_FALSE(base::ContainsKey(owners, value)); \
-  ASSERT_FALSE(base::ContainsKey(freed, value))
+#define ASSERT_UNOWNED(value)                  \
+  ASSERT_FALSE(base::Contains(owners, value)); \
+  ASSERT_FALSE(base::Contains(freed, value))
 
-#define ASSERT_FREED(value)                       \
-  ASSERT_FALSE(base::ContainsKey(owners, value)); \
-  ASSERT_TRUE(base::ContainsKey(freed, value))
+#define ASSERT_FREED(value)                    \
+  ASSERT_FALSE(base::Contains(owners, value)); \
+  ASSERT_TRUE(base::Contains(freed, value))
 
   // Constructor.
   {
diff --git a/base/scoped_observer.h b/base/scoped_observer.h
index 7f1d6fba..ecad6c0 100644
--- a/base/scoped_observer.h
+++ b/base/scoped_observer.h
@@ -47,7 +47,7 @@
   }
 
   bool IsObserving(Source* source) const {
-    return base::ContainsValue(sources_, source);
+    return base::Contains(sources_, source);
   }
 
   bool IsObservingSources() const { return !sources_.empty(); }
diff --git a/base/stl_util.h b/base/stl_util.h
index 3a4dd7f..18d44616b 100644
--- a/base/stl_util.h
+++ b/base/stl_util.h
@@ -221,6 +221,8 @@
 
 // Test to see if a set or map contains a particular key.
 // Returns true if the key is in the collection.
+// TODO(crbug.com/970209): Replace usages of ContainsKey() with Contains() and
+// remove this method.
 template <typename Collection, typename Key>
 bool ContainsKey(const Collection& collection, const Key& key) {
   return Contains(collection, key);
@@ -228,6 +230,8 @@
 
 // Test to see if a collection like a vector contains a particular value.
 // Returns true if the value is in the collection.
+// TODO(crbug.com/970209): Replace usages of ContainsValue() with Contains() and
+// remove this method.
 template <typename Collection, typename Value>
 bool ContainsValue(const Collection& collection, const Value& value) {
   return Contains(collection, value);
diff --git a/base/task/thread_pool/worker_thread_stack.cc b/base/task/thread_pool/worker_thread_stack.cc
index f6f2610..40eadde 100644
--- a/base/task/thread_pool/worker_thread_stack.cc
+++ b/base/task/thread_pool/worker_thread_stack.cc
@@ -41,7 +41,7 @@
 }
 
 bool WorkerThreadStack::Contains(const WorkerThread* worker) const {
-  return ContainsValue(stack_, worker);
+  return base::Contains(stack_, worker);
 }
 
 void WorkerThreadStack::Remove(const WorkerThread* worker) {
diff --git a/base/test/clang_coverage.h b/base/test/clang_coverage.h
index 44337f1f..ed2e3d7 100644
--- a/base/test/clang_coverage.h
+++ b/base/test/clang_coverage.h
@@ -5,6 +5,12 @@
 #ifndef BASE_TEST_CLANG_COVERAGE_H_
 #define BASE_TEST_CLANG_COVERAGE_H_
 
+#include "base/clang_coverage_buildflags.h"
+
+#if !BUILDFLAG(CLANG_COVERAGE)
+#error "Clang coverage can only be used if CLANG_COVERAGE macro is defined"
+#endif
+
 namespace base {
 
 // Write out the accumulated code coverage profile to the configured file.
@@ -13,7 +19,6 @@
 // (or triggering a debug crash), where the automatic at-exit writer will not
 // be invoked.
 // This call is thread-safe, and will write profiling data at-most-once.
-// Call-sites invoke this API only if the CLANG_COVERAGE macro is defined.
 void WriteClangCoverageProfile();
 
 }  // namespace base
diff --git a/base/test/launcher/test_launcher.cc b/base/test/launcher/test_launcher.cc
index 56b197c..b3951485 100644
--- a/base/test/launcher/test_launcher.cc
+++ b/base/test/launcher/test_launcher.cc
@@ -1242,7 +1242,7 @@
   std::vector<TestInfo> tests_to_run;
   for (const TestInfo& test_info : tests_) {
     // If any test has a matching disabled test, fail and log for audit.
-    if (base::ContainsKey(disabled_tests, test_info.GetFullName())) {
+    if (base::Contains(disabled_tests, test_info.GetFullName())) {
       LOG(ERROR) << test_info.GetFullName()
                  << " duplicated by a DISABLED_ test";
       result = false;
diff --git a/base/test/launcher/unit_test_launcher.cc b/base/test/launcher/unit_test_launcher.cc
index cfd517b3..bcac932 100644
--- a/base/test/launcher/unit_test_launcher.cc
+++ b/base/test/launcher/unit_test_launcher.cc
@@ -282,7 +282,7 @@
   std::vector<TestResult> final_results;
 
   for (const auto& i : test_names) {
-    if (ContainsKey(results_map, i)) {
+    if (Contains(results_map, i)) {
       TestResult test_result = results_map[i];
       if (test_result.status == TestResult::TEST_CRASH) {
         if (was_timeout) {
diff --git a/base/test/scoped_feature_list.cc b/base/test/scoped_feature_list.cc
index 01a5e75..0cc910c 100644
--- a/base/test/scoped_feature_list.cc
+++ b/base/test/scoped_feature_list.cc
@@ -68,8 +68,8 @@
   for (StringPiece feature : features_list) {
     StringPiece feature_name = GetFeatureName(feature);
 
-    if (ContainsValue(merged_features->enabled_feature_list, feature_name) ||
-        ContainsValue(merged_features->disabled_feature_list, feature_name))
+    if (Contains(merged_features->enabled_feature_list, feature_name) ||
+        Contains(merged_features->disabled_feature_list, feature_name))
       continue;
 
     if (override_state == FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE) {
diff --git a/base/threading/thread_id_name_manager.cc b/base/threading/thread_id_name_manager.cc
index a0ced2c5..ba2f9b4 100644
--- a/base/threading/thread_id_name_manager.cc
+++ b/base/threading/thread_id_name_manager.cc
@@ -10,6 +10,7 @@
 #include "base/logging.h"
 #include "base/memory/singleton.h"
 #include "base/no_destructor.h"
+#include "base/stl_util.h"
 #include "base/strings/string_util.h"
 #include "base/threading/thread_local.h"
 #include "base/trace_event/heap_profiler_allocation_context_tracker.h"
@@ -26,6 +27,8 @@
 }
 }
 
+ThreadIdNameManager::Observer::~Observer() = default;
+
 ThreadIdNameManager::ThreadIdNameManager()
     : main_process_name_(nullptr), main_process_id_(kInvalidThreadId) {
   g_default_name = new std::string(kDefaultName);
@@ -53,9 +56,16 @@
       name_to_interned_name_[kDefaultName];
 }
 
-void ThreadIdNameManager::InstallSetNameCallback(SetNameCallback callback) {
+void ThreadIdNameManager::AddObserver(Observer* obs) {
   AutoLock locked(lock_);
-  set_name_callback_ = std::move(callback);
+  DCHECK(!base::Contains(observers_, obs));
+  observers_.push_back(obs);
+}
+
+void ThreadIdNameManager::RemoveObserver(Observer* obs) {
+  AutoLock locked(lock_);
+  DCHECK(base::Contains(observers_, obs));
+  base::Erase(observers_, obs);
 }
 
 void ThreadIdNameManager::SetName(const std::string& name) {
@@ -74,9 +84,8 @@
     auto id_to_handle_iter = thread_id_to_handle_.find(id);
 
     GetThreadNameTLS().Set(const_cast<char*>(leaked_str->c_str()));
-    if (set_name_callback_) {
-      set_name_callback_.Run(leaked_str->c_str());
-    }
+    for (Observer* obs : observers_)
+      obs->OnThreadNameChanged(leaked_str->c_str());
 
     // The main thread of a process will not be created as a Thread object which
     // means there is no PlatformThreadHandler registered.
diff --git a/base/threading/thread_id_name_manager.h b/base/threading/thread_id_name_manager.h
index f17dc1a4..e413da5 100644
--- a/base/threading/thread_id_name_manager.h
+++ b/base/threading/thread_id_name_manager.h
@@ -7,10 +7,12 @@
 
 #include <map>
 #include <string>
+#include <vector>
 
 #include "base/base_export.h"
 #include "base/callback.h"
 #include "base/macros.h"
+#include "base/observer_list.h"
 #include "base/synchronization/lock.h"
 #include "base/threading/platform_thread.h"
 
@@ -25,14 +27,24 @@
 
   static const char* GetDefaultInternedString();
 
+  class BASE_EXPORT Observer {
+   public:
+    virtual ~Observer();
+
+    // Called on the thread whose name is changing, immediately after the name
+    // is set. |name| is a pointer to a C string that is guaranteed to remain
+    // valid for the duration of the process.
+    //
+    // NOTE: Will be called while ThreadIdNameManager's lock is held, so don't
+    // call back into it.
+    virtual void OnThreadNameChanged(const char* name) = 0;
+  };
+
   // Register the mapping between a thread |id| and |handle|.
   void RegisterThread(PlatformThreadHandle::Handle handle, PlatformThreadId id);
 
-  // The callback is called on the thread, immediately after the name is set.
-  // |name| is a pointer to a C string that is guaranteed to remain valid for
-  // the duration of the process.
-  using SetNameCallback = base::RepeatingCallback<void(const char* name)>;
-  void InstallSetNameCallback(SetNameCallback callback);
+  void AddObserver(Observer*);
+  void RemoveObserver(Observer*);
 
   // Set the name for the current thread.
   void SetName(const std::string& name);
@@ -70,7 +82,9 @@
   std::string* main_process_name_;
   PlatformThreadId main_process_id_;
 
-  SetNameCallback set_name_callback_;
+  // There's no point using a base::ObserverList behind a lock, so we just use
+  // an std::vector instead.
+  std::vector<Observer*> observers_;
 
   DISALLOW_COPY_AND_ASSIGN(ThreadIdNameManager);
 };
diff --git a/base/threading/thread_local_storage_perftest.cc b/base/threading/thread_local_storage_perftest.cc
index 786a091..198775c 100644
--- a/base/threading/thread_local_storage_perftest.cc
+++ b/base/threading/thread_local_storage_perftest.cc
@@ -15,9 +15,17 @@
 #include "base/threading/simple_thread.h"
 #include "base/threading/thread_local_storage.h"
 #include "base/time/time.h"
+#include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "testing/perf/perf_test.h"
 
+#if defined(OS_WIN)
+#include <windows.h>
+#include "base/win/windows_types.h"
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include <pthread.h>
+#endif
+
 namespace base {
 namespace internal {
 
@@ -134,9 +142,59 @@
   auto read = [&]() { return reinterpret_cast<intptr_t>(tls.Get()); };
   auto write = [&](intptr_t value) { tls.Set(reinterpret_cast<void*>(value)); };
 
-  Benchmark("ThreadLocalStorage", read, write, 1000000, 1);
-  Benchmark("ThreadLocalStorage 4 threads", read, write, 1000000, 4);
+  Benchmark("ThreadLocalStorage", read, write, 10000000, 1);
+  Benchmark("ThreadLocalStorage 4 threads", read, write, 10000000, 4);
 }
 
+#if defined(OS_WIN)
+
+void WINAPI destroy(void*) {}
+
+TEST_F(ThreadLocalStoragePerfTest, PlatformFls) {
+  DWORD key = FlsAlloc(destroy);
+  ASSERT_NE(PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES, key);
+
+  auto read = [&]() { return reinterpret_cast<intptr_t>(FlsGetValue(key)); };
+  auto write = [&](intptr_t value) {
+    FlsSetValue(key, reinterpret_cast<void*>(value));
+  };
+
+  Benchmark("PlatformFls", read, write, 10000000, 1);
+  Benchmark("PlatformFls 4 threads", read, write, 10000000, 4);
+}
+
+TEST_F(ThreadLocalStoragePerfTest, PlatformTls) {
+  DWORD key = TlsAlloc();
+  ASSERT_NE(PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES, key);
+
+  auto read = [&]() { return reinterpret_cast<intptr_t>(TlsGetValue(key)); };
+  auto write = [&](intptr_t value) {
+    TlsSetValue(key, reinterpret_cast<void*>(value));
+  };
+
+  Benchmark("PlatformTls", read, write, 10000000, 1);
+  Benchmark("PlatformTls 4 threads", read, write, 10000000, 4);
+}
+
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+
+TEST_F(ThreadLocalStoragePerfTest, PlatformTls) {
+  pthread_key_t key;
+  ASSERT_FALSE(pthread_key_create(&key, [](void*) {}));
+  ASSERT_NE(PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES, key);
+
+  auto read = [&]() {
+    return reinterpret_cast<intptr_t>(pthread_getspecific(key));
+  };
+  auto write = [&](intptr_t value) {
+    pthread_setspecific(key, reinterpret_cast<void*>(value));
+  };
+
+  Benchmark("PlatformTls", read, write, 10000000, 1);
+  Benchmark("PlatformTls 4 threads", read, write, 10000000, 4);
+}
+
+#endif
+
 }  // namespace internal
 }  // namespace base
\ No newline at end of file
diff --git a/base/trace_event/trace_log.cc b/base/trace_event/trace_log.cc
index 44ee37c..fe765a0 100644
--- a/base/trace_event/trace_log.cc
+++ b/base/trace_event/trace_log.cc
@@ -782,7 +782,7 @@
 
 bool TraceLog::HasEnabledStateObserver(EnabledStateObserver* listener) const {
   AutoLock lock(observers_lock_);
-  return ContainsValue(enabled_state_observers_, listener);
+  return Contains(enabled_state_observers_, listener);
 }
 
 void TraceLog::AddAsyncEnabledStateObserver(
@@ -800,7 +800,7 @@
 bool TraceLog::HasAsyncEnabledStateObserver(
     AsyncEnabledStateObserver* listener) const {
   AutoLock lock(observers_lock_);
-  return ContainsKey(async_observers_, listener);
+  return Contains(async_observers_, listener);
 }
 
 TraceLogStatus TraceLog::GetStatus() const {
@@ -1212,7 +1212,7 @@
         std::vector<StringPiece> existing_names = base::SplitStringPiece(
             existing_name->second, ",", base::KEEP_WHITESPACE,
             base::SPLIT_WANT_NONEMPTY);
-        if (!ContainsValue(existing_names, new_name)) {
+        if (!Contains(existing_names, new_name)) {
           if (!existing_names.empty())
             existing_name->second.push_back(',');
           existing_name->second.append(new_name);
diff --git a/base/values.cc b/base/values.cc
index 03acb86..7d042cd 100644
--- a/base/values.cc
+++ b/base/values.cc
@@ -1706,7 +1706,7 @@
 
 bool ListValue::AppendIfNotPresent(std::unique_ptr<Value> in_value) {
   DCHECK(in_value);
-  if (ContainsValue(list_, *in_value))
+  if (Contains(list_, *in_value))
     return false;
 
   list_.push_back(std::move(*in_value));
diff --git a/base/win/win_util_unittest.cc b/base/win/win_util_unittest.cc
index 5999fb9..1178199e 100644
--- a/base/win/win_util_unittest.cc
+++ b/base/win/win_util_unittest.cc
@@ -73,7 +73,7 @@
   ASSERT_NE(static_cast<HMODULE>(NULL), new_dll.get());
   ASSERT_TRUE(GetLoadedModulesSnapshot(::GetCurrentProcess(), &snapshot));
   ASSERT_GT(snapshot.size(), original_snapshot_size);
-  ASSERT_TRUE(ContainsValue(snapshot, new_dll.get()));
+  ASSERT_TRUE(Contains(snapshot, new_dll.get()));
 }
 
 TEST(BaseWinUtilTest, TestUint32ToInvalidHandle) {
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index f9b22ea..52913649 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-8911371438377102624
\ No newline at end of file
+8911315473209928720
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 4e41237b..0745611d 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-8911375035762188400
\ No newline at end of file
+8911318344250807168
\ No newline at end of file
diff --git a/cc/layers/heads_up_display_layer_impl.cc b/cc/layers/heads_up_display_layer_impl.cc
index 19b7422..850fbc4 100644
--- a/cc/layers/heads_up_display_layer_impl.cc
+++ b/cc/layers/heads_up_display_layer_impl.cc
@@ -320,14 +320,13 @@
       auto backing = std::make_unique<HudSoftwareBacking>();
       backing->layer_tree_frame_sink = layer_tree_frame_sink;
       backing->shared_bitmap_id = viz::SharedBitmap::GenerateId();
-      base::MappedReadOnlyRegion mapped_region =
+      base::MappedReadOnlyRegion shm =
           viz::bitmap_allocation::AllocateSharedBitmap(pool_resource.size(),
                                                        pool_resource.format());
-      backing->shared_mapping = std::move(mapped_region.mapping);
+      backing->shared_mapping = std::move(shm.mapping);
 
-      layer_tree_frame_sink->DidAllocateSharedBitmap(
-          viz::bitmap_allocation::ToMojoHandle(std::move(mapped_region.region)),
-          backing->shared_bitmap_id);
+      layer_tree_frame_sink->DidAllocateSharedBitmap(std::move(shm.region),
+                                                     backing->shared_bitmap_id);
 
       pool_resource.set_software_backing(std::move(backing));
     }
diff --git a/cc/layers/texture_layer_impl.cc b/cc/layers/texture_layer_impl.cc
index a51f6789..3f82daf 100644
--- a/cc/layers/texture_layer_impl.cc
+++ b/cc/layers/texture_layer_impl.cc
@@ -110,8 +110,7 @@
 
   LayerTreeFrameSink* sink = layer_tree_impl()->layer_tree_frame_sink();
   for (const auto& pair : to_register_bitmaps_) {
-    sink->DidAllocateSharedBitmap(viz::bitmap_allocation::ToMojoHandle(
-                                      pair.second->shared_region().Duplicate()),
+    sink->DidAllocateSharedBitmap(pair.second->shared_region().Duplicate(),
                                   pair.first);
   }
   // All |to_register_bitmaps_| have been registered above, so we can move them
diff --git a/cc/mojo_embedder/async_layer_tree_frame_sink.cc b/cc/mojo_embedder/async_layer_tree_frame_sink.cc
index 4459afb..f34b638 100644
--- a/cc/mojo_embedder/async_layer_tree_frame_sink.cc
+++ b/cc/mojo_embedder/async_layer_tree_frame_sink.cc
@@ -293,10 +293,10 @@
 }
 
 void AsyncLayerTreeFrameSink::DidAllocateSharedBitmap(
-    mojo::ScopedSharedBufferHandle buffer,
+    base::ReadOnlySharedMemoryRegion region,
     const viz::SharedBitmapId& id) {
   DCHECK(compositor_frame_sink_ptr_);
-  compositor_frame_sink_ptr_->DidAllocateSharedBitmap(std::move(buffer), id);
+  compositor_frame_sink_ptr_->DidAllocateSharedBitmap(std::move(region), id);
 }
 
 void AsyncLayerTreeFrameSink::DidDeleteSharedBitmap(
diff --git a/cc/mojo_embedder/async_layer_tree_frame_sink.h b/cc/mojo_embedder/async_layer_tree_frame_sink.h
index 5ed49023..95d4c6d 100644
--- a/cc/mojo_embedder/async_layer_tree_frame_sink.h
+++ b/cc/mojo_embedder/async_layer_tree_frame_sink.h
@@ -9,6 +9,7 @@
 #include <string>
 #include <vector>
 
+#include "base/memory/read_only_shared_memory_region.h"
 #include "base/memory/weak_ptr.h"
 #include "base/single_thread_task_runner.h"
 #include "cc/mojo_embedder/mojo_embedder_export.h"
@@ -123,7 +124,7 @@
                              bool hit_test_data_changed,
                              bool show_hit_test_borders) override;
   void DidNotProduceFrame(const viz::BeginFrameAck& ack) override;
-  void DidAllocateSharedBitmap(mojo::ScopedSharedBufferHandle buffer,
+  void DidAllocateSharedBitmap(base::ReadOnlySharedMemoryRegion region,
                                const viz::SharedBitmapId& id) override;
   void DidDeleteSharedBitmap(const viz::SharedBitmapId& id) override;
   void ForceAllocateNewId() override;
diff --git a/cc/raster/bitmap_raster_buffer_provider.cc b/cc/raster/bitmap_raster_buffer_provider.cc
index 754ff69..db62527 100644
--- a/cc/raster/bitmap_raster_buffer_provider.cc
+++ b/cc/raster/bitmap_raster_buffer_provider.cc
@@ -108,12 +108,11 @@
     auto backing = std::make_unique<BitmapSoftwareBacking>();
     backing->frame_sink = frame_sink_;
     backing->shared_bitmap_id = viz::SharedBitmap::GenerateId();
-    base::MappedReadOnlyRegion mapped_region =
+    base::MappedReadOnlyRegion shm =
         viz::bitmap_allocation::AllocateSharedBitmap(size, viz::RGBA_8888);
-    backing->mapping = std::move(mapped_region.mapping);
-    frame_sink_->DidAllocateSharedBitmap(
-        viz::bitmap_allocation::ToMojoHandle(std::move(mapped_region.region)),
-        backing->shared_bitmap_id);
+    backing->mapping = std::move(shm.mapping);
+    frame_sink_->DidAllocateSharedBitmap(std::move(shm.region),
+                                         backing->shared_bitmap_id);
 
     resource.set_software_backing(std::move(backing));
   }
diff --git a/cc/test/fake_layer_tree_frame_sink.cc b/cc/test/fake_layer_tree_frame_sink.cc
index 0d2a0b50..740b56e 100644
--- a/cc/test/fake_layer_tree_frame_sink.cc
+++ b/cc/test/fake_layer_tree_frame_sink.cc
@@ -85,7 +85,7 @@
 }
 
 void FakeLayerTreeFrameSink::DidAllocateSharedBitmap(
-    mojo::ScopedSharedBufferHandle buffer,
+    base::ReadOnlySharedMemoryRegion region,
     const viz::SharedBitmapId& id) {
   DCHECK(!base::Contains(shared_bitmaps_, id));
   shared_bitmaps_.push_back(id);
diff --git a/cc/test/fake_layer_tree_frame_sink.h b/cc/test/fake_layer_tree_frame_sink.h
index 50ca127..5eb1129 100644
--- a/cc/test/fake_layer_tree_frame_sink.h
+++ b/cc/test/fake_layer_tree_frame_sink.h
@@ -11,6 +11,7 @@
 #include "base/callback.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
+#include "base/memory/read_only_shared_memory_region.h"
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
 #include "cc/trees/layer_tree_frame_sink.h"
@@ -104,7 +105,7 @@
                              bool hit_test_data_changed,
                              bool show_hit_test_borders) override;
   void DidNotProduceFrame(const viz::BeginFrameAck& ack) override;
-  void DidAllocateSharedBitmap(mojo::ScopedSharedBufferHandle buffer,
+  void DidAllocateSharedBitmap(base::ReadOnlySharedMemoryRegion region,
                                const viz::SharedBitmapId& id) override;
   void DidDeleteSharedBitmap(const viz::SharedBitmapId& id) override;
 
diff --git a/cc/test/test_layer_tree_frame_sink.cc b/cc/test/test_layer_tree_frame_sink.cc
index e6b51c5b..da1736f 100644
--- a/cc/test/test_layer_tree_frame_sink.cc
+++ b/cc/test/test_layer_tree_frame_sink.cc
@@ -204,10 +204,10 @@
 }
 
 void TestLayerTreeFrameSink::DidAllocateSharedBitmap(
-    mojo::ScopedSharedBufferHandle buffer,
+    base::ReadOnlySharedMemoryRegion region,
     const viz::SharedBitmapId& id) {
-  bool ok = shared_bitmap_manager_->ChildAllocatedSharedBitmap(
-      viz::bitmap_allocation::FromMojoHandle(std::move(buffer)).Map(), id);
+  bool ok =
+      shared_bitmap_manager_->ChildAllocatedSharedBitmap(region.Map(), id);
   DCHECK(ok);
   owned_bitmaps_.insert(id);
 }
diff --git a/cc/test/test_layer_tree_frame_sink.h b/cc/test/test_layer_tree_frame_sink.h
index 72a89e7..fe43725 100644
--- a/cc/test/test_layer_tree_frame_sink.h
+++ b/cc/test/test_layer_tree_frame_sink.h
@@ -94,7 +94,7 @@
                              bool hit_test_data_changed,
                              bool show_hit_test_borders) override;
   void DidNotProduceFrame(const viz::BeginFrameAck& ack) override;
-  void DidAllocateSharedBitmap(mojo::ScopedSharedBufferHandle buffer,
+  void DidAllocateSharedBitmap(base::ReadOnlySharedMemoryRegion buffer,
                                const viz::SharedBitmapId& id) override;
   void DidDeleteSharedBitmap(const viz::SharedBitmapId& id) override;
 
diff --git a/cc/trees/layer_tree_frame_sink.h b/cc/trees/layer_tree_frame_sink.h
index 76634a7..7a0444c6 100644
--- a/cc/trees/layer_tree_frame_sink.h
+++ b/cc/trees/layer_tree_frame_sink.h
@@ -8,6 +8,7 @@
 #include <deque>
 #include <memory>
 
+#include "base/memory/read_only_shared_memory_region.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "base/single_thread_task_runner.h"
@@ -127,7 +128,7 @@
   virtual void DidNotProduceFrame(const viz::BeginFrameAck& ack) = 0;
 
   // viz::SharedBitmapReporter implementation.
-  void DidAllocateSharedBitmap(mojo::ScopedSharedBufferHandle buffer,
+  void DidAllocateSharedBitmap(base::ReadOnlySharedMemoryRegion region,
                                const viz::SharedBitmapId& id) override = 0;
   void DidDeleteSharedBitmap(const viz::SharedBitmapId& id) override = 0;
 
diff --git a/cc/trees/layer_tree_frame_sink_unittest.cc b/cc/trees/layer_tree_frame_sink_unittest.cc
index 58334b8..5eabeb0 100644
--- a/cc/trees/layer_tree_frame_sink_unittest.cc
+++ b/cc/trees/layer_tree_frame_sink_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "cc/trees/layer_tree_frame_sink.h"
 
+#include "base/memory/read_only_shared_memory_region.h"
 #include "base/single_thread_task_runner.h"
 #include "base/test/test_simple_task_runner.h"
 #include "cc/test/fake_layer_tree_frame_sink_client.h"
@@ -34,7 +35,7 @@
     client_->DidReceiveCompositorFrameAck();
   }
   void DidNotProduceFrame(const viz::BeginFrameAck& ack) override {}
-  void DidAllocateSharedBitmap(mojo::ScopedSharedBufferHandle buffer,
+  void DidAllocateSharedBitmap(base::ReadOnlySharedMemoryRegion region,
                                const viz::SharedBitmapId& id) override {}
   void DidDeleteSharedBitmap(const viz::SharedBitmapId& id) override {}
 };
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index b372ed9..302f2e6 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -5361,7 +5361,7 @@
   GLenum texture_target = GL_TEXTURE_2D;
   // For software compositing, shared memory will be allocated and the
   // UIResource will be copied into it.
-  base::MappedReadOnlyRegion mapped_region;
+  base::MappedReadOnlyRegion shm;
   viz::SharedBitmapId shared_bitmap_id;
   bool overlay_candidate = false;
 
@@ -5379,8 +5379,7 @@
                                                    BufferFormat(format), caps);
     }
   } else {
-    mapped_region =
-        viz::bitmap_allocation::AllocateSharedBitmap(upload_size, format);
+    shm = viz::bitmap_allocation::AllocateSharedBitmap(upload_size, format);
     shared_bitmap_id = viz::SharedBitmap::GenerateId();
   }
 
@@ -5405,7 +5404,7 @@
           SkImageInfo::MakeN32Premul(gfx::SizeToSkISize(upload_size));
 
       sk_sp<SkSurface> surface = SkSurface::MakeRasterDirect(
-          dst_info, mapped_region.mapping.memory(), dst_info.minRowBytes());
+          dst_info, shm.mapping.memory(), dst_info.minRowBytes());
       surface->getCanvas()->writePixels(
           src_info, const_cast<uint8_t*>(bitmap.GetPixels()),
           src_info.minRowBytes(), 0, 0);
@@ -5441,7 +5440,7 @@
       SkImageInfo dst_info =
           SkImageInfo::MakeN32Premul(gfx::SizeToSkISize(upload_size));
       scaled_surface = SkSurface::MakeRasterDirect(
-          dst_info, mapped_region.mapping.memory(), dst_info.minRowBytes());
+          dst_info, shm.mapping.memory(), dst_info.minRowBytes());
     }
     SkCanvas* scaled_canvas = scaled_surface->getCanvas();
     scaled_canvas->scale(canvas_scale_x, canvas_scale_y);
@@ -5484,9 +5483,8 @@
         overlay_candidate);
     transferable.format = format;
   } else {
-    layer_tree_frame_sink_->DidAllocateSharedBitmap(
-        viz::bitmap_allocation::ToMojoHandle(std::move(mapped_region.region)),
-        shared_bitmap_id);
+    layer_tree_frame_sink_->DidAllocateSharedBitmap(std::move(shm.region),
+                                                    shared_bitmap_id);
     transferable = viz::TransferableResource::MakeSoftware(shared_bitmap_id,
                                                            upload_size, format);
   }
@@ -5504,7 +5502,7 @@
   data.opaque = bitmap.GetOpaque();
   data.format = format;
   data.shared_bitmap_id = shared_bitmap_id;
-  data.shared_mapping = std::move(mapped_region.mapping);
+  data.shared_mapping = std::move(shm.mapping);
   data.mailbox = mailbox;
   data.resource_id_for_export = id;
   ui_resource_map_[uid] = std::move(data);
diff --git a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingMetricsRecorder.java b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingMetricsRecorder.java
index f725ec1e..6edd12d 100644
--- a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingMetricsRecorder.java
+++ b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingMetricsRecorder.java
@@ -22,6 +22,8 @@
             "KeyboardAccessory.AccessorySheetSuggestionsSelected";
     private static final String UMA_KEYBOARD_ACCESSORY_SHEET_TYPE_SUFFIX_PASSWORDS = "Passwords";
     private static final String UMA_KEYBOARD_ACCESSORY_SHEET_TYPE_SUFFIX_ADDRESSES = "Addresses";
+    private static final String UMA_KEYBOARD_ACCESSORY_SHEET_TYPE_SUFFIX_TOUCH_TO_FILL =
+            "TouchToFill";
 
     /**
      * The Recorder itself should be stateless and have no need for an instance.
@@ -42,6 +44,8 @@
                 return baseHistogram + "." + UMA_KEYBOARD_ACCESSORY_SHEET_TYPE_SUFFIX_PASSWORDS;
             case AccessoryTabType.ADDRESSES:
                 return baseHistogram + "." + UMA_KEYBOARD_ACCESSORY_SHEET_TYPE_SUFFIX_ADDRESSES;
+            case AccessoryTabType.TOUCH_TO_FILL:
+                return baseHistogram + "." + UMA_KEYBOARD_ACCESSORY_SHEET_TYPE_SUFFIX_TOUCH_TO_FILL;
         }
         assert false : "Undefined histogram for tab type " + tabType + " !";
         return "";
@@ -106,6 +110,10 @@
                 // TODO(crbug.com/965494): Consider splitting and/or separate recording.
                 suggestionRecordingType = AccessorySuggestionType.ADDRESS_INFO;
                 break;
+            case AccessoryTabType.TOUCH_TO_FILL:
+                suggestionRecordingType = AccessorySuggestionType.TOUCH_TO_FILL_INFO;
+                break;
+
             case AccessoryTabType.ALL:
                 throw new InvalidParameterException("Unable to handle tabType: " + tabType);
         }
diff --git a/chrome/android/java/res/layout/edit_url_suggestion_layout.xml b/chrome/android/java/res/layout/edit_url_suggestion_layout.xml
index bb5ea00..f23b9f4 100644
--- a/chrome/android/java/res/layout/edit_url_suggestion_layout.xml
+++ b/chrome/android/java/res/layout/edit_url_suggestion_layout.xml
@@ -41,14 +41,31 @@
 
     </LinearLayout>
 
+    <Space
+        android:id="@+id/edit_url_space"
+        android:layout_width="@dimen/omnibox_suggestion_start_offset_without_icon"
+        android:layout_height="match_parent"
+        android:layout_alignParentStart="true" />
+
+    <ImageView
+        android:id="@+id/edit_url_favicon"
+        android:layout_width="@dimen/omnibox_suggestion_favicon_size"
+        android:layout_height="@dimen/omnibox_suggestion_favicon_size"
+        android:layout_centerVertical="true"
+        android:layout_marginStart="@dimen/omnibox_suggestion_24dp_icon_margin_start"
+        android:layout_marginEnd="@dimen/omnibox_suggestion_24dp_icon_margin_end"
+        android:layout_toEndOf="@id/edit_url_space"
+        android:importantForAccessibility="no"
+        android:visibility="gone"
+        android:scaleType="centerInside" />
+
     <LinearLayout
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_alignParentStart="true"
         android:layout_centerVertical="true"
         android:layout_toStartOf="@id/edit_url_suggestion_icons"
-        android:orientation="vertical"
-        android:layout_marginStart="@dimen/omnibox_suggestion_start_offset_without_icon">
+        android:layout_toEndOf="@id/edit_url_favicon"
+        android:orientation="vertical">
 
         <TextView
             android:id="@+id/title_text_view"
diff --git a/chrome/android/java/res/layout/omnibox_answer_suggestion.xml b/chrome/android/java/res/layout/omnibox_answer_suggestion.xml
index 72d5bb7..cbd6c39 100644
--- a/chrome/android/java/res/layout/omnibox_answer_suggestion.xml
+++ b/chrome/android/java/res/layout/omnibox_answer_suggestion.xml
@@ -27,8 +27,8 @@
             android:id="@+id/omnibox_answer_icon"
             android:layout_centerVertical="true"
             android:layout_height="36dp"
-            android:layout_marginEnd="10dp"
-            android:layout_marginStart="@dimen/omnibox_suggestion_icon_margin_start"
+            android:layout_marginEnd="@dimen/omnibox_suggestion_36dp_icon_margin_end"
+            android:layout_marginStart="@dimen/omnibox_suggestion_36dp_icon_margin_start"
             android:layout_width="36dp"
             android:scaleType="fitCenter" />
 
diff --git a/chrome/android/java/res/layout/omnibox_entity_suggestion.xml b/chrome/android/java/res/layout/omnibox_entity_suggestion.xml
index 2f76cf0..98c817f 100644
--- a/chrome/android/java/res/layout/omnibox_entity_suggestion.xml
+++ b/chrome/android/java/res/layout/omnibox_entity_suggestion.xml
@@ -25,9 +25,9 @@
             android:layout_width="@dimen/omnibox_suggestion_entity_icon_size"
             android:layout_height="@dimen/omnibox_suggestion_entity_icon_size"
             android:layout_centerVertical="true"
-            android:layout_marginEnd="8dp"
-            android:layout_marginStart="@dimen/omnibox_suggestion_icon_margin_start"
-            android:contentDescription="@null" />
+            android:layout_marginEnd="@dimen/omnibox_suggestion_36dp_icon_margin_end"
+            android:layout_marginStart="@dimen/omnibox_suggestion_36dp_icon_margin_start"
+            android:contentDescription="@null"/>
 
         <TextView
             android:id="@+id/omnibox_entity_subject_text"
diff --git a/chrome/android/java/res/values-sw600dp/dimens.xml b/chrome/android/java/res/values-sw600dp/dimens.xml
index dbdb8a9..aa9a859 100644
--- a/chrome/android/java/res/values-sw600dp/dimens.xml
+++ b/chrome/android/java/res/values-sw600dp/dimens.xml
@@ -34,7 +34,10 @@
     <dimen name="omnibox_suggestion_start_offset_without_icon">@dimen/location_bar_icon_width</dimen>
     <dimen name="omnibox_suggestion_start_offset_with_icon">@dimen/omnibox_suggestion_start_offset_without_icon</dimen>
 
-    <dimen name="omnibox_suggestion_icon_margin_start">0dp</dimen>
+    <dimen name="omnibox_suggestion_36dp_icon_margin_start">0dp</dimen>
+    <dimen name="omnibox_suggestion_36dp_icon_margin_end">8dp</dimen>
+    <dimen name="omnibox_suggestion_24dp_icon_margin_start">8dp</dimen>
+    <dimen name="omnibox_suggestion_24dp_icon_margin_end">8dp</dimen>
 
     <!-- NTP dimensions -->
     <dimen name="ntp_search_box_transition_length">60dp</dimen>
diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res/values/dimens.xml
index 6892ee4..0ef4e15 100644
--- a/chrome/android/java/res/values/dimens.xml
+++ b/chrome/android/java/res/values/dimens.xml
@@ -301,7 +301,10 @@
     <dimen name="omnibox_suggestion_start_offset_without_icon">18dp</dimen>
     <dimen name="omnibox_suggestion_start_offset_with_icon">56dp</dimen>
 
-    <dimen name="omnibox_suggestion_icon_margin_start">10dp</dimen>
+    <dimen name="omnibox_suggestion_36dp_icon_margin_start">10dp</dimen>
+    <dimen name="omnibox_suggestion_36dp_icon_margin_end">10dp</dimen>
+    <dimen name="omnibox_suggestion_24dp_icon_margin_start">16dp</dimen>
+    <dimen name="omnibox_suggestion_24dp_icon_margin_end">16dp</dimen>
     <dimen name="omnibox_suggestion_favicon_size">24dp</dimen>
     <dimen name="omnibox_suggestion_entity_icon_size">36dp</dimen>
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
index f3c289e..a3d8820 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
@@ -159,7 +159,7 @@
         mBasicSuggestionProcessor = new BasicSuggestionProcessor(mContext, this, textProvider);
         mAnswerSuggestionProcessor = new AnswerSuggestionProcessor(mContext, this, textProvider);
         mEditUrlProcessor = new EditUrlSuggestionProcessor(
-                delegate, (suggestion) -> onSelection(suggestion, 0));
+                mContext, this, delegate, (suggestion) -> onSelection(suggestion, 0));
         mEntitySuggestionProcessor = new EntitySuggestionProcessor(mContext, this);
     }
 
@@ -430,6 +430,7 @@
     void setAutocompleteProfile(Profile profile) {
         mAutocomplete.setProfile(profile);
         mBasicSuggestionProcessor.setProfile(profile);
+        mEditUrlProcessor.setProfile(profile);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionProcessor.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionProcessor.java
index 7b80b12..ded877e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionProcessor.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionProcessor.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.omnibox.suggestions.editurl;
 
 import android.content.Context;
+import android.graphics.Bitmap;
 import android.support.annotation.IntDef;
 import android.text.TextUtils;
 import android.view.LayoutInflater;
@@ -16,12 +17,16 @@
 import org.chromium.base.metrics.CachedMetrics.EnumeratedHistogramSample;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityTabProvider;
+import org.chromium.chrome.browser.ChromeFeatureList;
+import org.chromium.chrome.browser.favicon.LargeIconBridge;
 import org.chromium.chrome.browser.omnibox.OmniboxSuggestionType;
 import org.chromium.chrome.browser.omnibox.UrlBar;
 import org.chromium.chrome.browser.omnibox.UrlBar.OmniboxAction;
 import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestion;
 import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionUiType;
 import org.chromium.chrome.browser.omnibox.suggestions.SuggestionProcessor;
+import org.chromium.chrome.browser.omnibox.suggestions.basic.SuggestionHost;
+import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.share.ShareMenuActionHandler;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.ui.base.Clipboard;
@@ -115,14 +120,32 @@
     /** Whether this processor should ignore all subsequent suggestion. */
     private boolean mIgnoreSuggestions;
 
+    /** Whether suggestion site favicons are enabled. */
+    private boolean mEnableSuggestionFavicons;
+
+    /** Edge size (in pixels) of the favicon. Used to request best matching favicon from cache. */
+    private final int mDesiredFaviconWidthPx;
+
+    /** Supplies Profile information. */
+    private Profile mCurrentUserProfile;
+
+    /** Supplies site favicons. */
+    private LargeIconBridge mLargeIconBridge;
+
+    /** Supplies additional control over suggestion model. */
+    private final SuggestionHost mSuggestionHost;
+
     /**
      * @param locationBarDelegate A means of modifying the location bar.
      * @param selectionHandler A mechanism for handling selection of the edit URL suggestion item.
      */
-    public EditUrlSuggestionProcessor(
+    public EditUrlSuggestionProcessor(Context context, SuggestionHost suggestionHost,
             LocationBarDelegate locationBarDelegate, SuggestionSelectionHandler selectionHandler) {
         mLocationBarDelegate = locationBarDelegate;
         mSelectionHandler = selectionHandler;
+        mDesiredFaviconWidthPx = context.getResources().getDimensionPixelSize(
+                R.dimen.omnibox_suggestion_favicon_size);
+        mSuggestionHost = suggestionHost;
     }
 
     /**
@@ -185,13 +208,42 @@
         model.set(EditUrlSuggestionProperties.TEXT_CLICK_LISTENER, this);
         model.set(EditUrlSuggestionProperties.BUTTON_CLICK_LISTENER, this);
 
+        // Lazily create LargeIconBridge in case Profile is reported ahead on Native initialized.
+        if (mEnableSuggestionFavicons && mLargeIconBridge == null && mCurrentUserProfile != null) {
+            mLargeIconBridge = new LargeIconBridge(mCurrentUserProfile);
+        }
+
+        if (mLargeIconBridge != null) {
+            mLargeIconBridge.getLargeIconForUrl(mLastProcessedSuggestion.getUrl(),
+                    mDesiredFaviconWidthPx,
+                    (Bitmap icon, int fallbackColor, boolean isFallbackColorDefault,
+                            int iconType) -> {
+                        if (!mSuggestionHost.isActiveModel(model)) return;
+                        model.set(EditUrlSuggestionProperties.SITE_FAVICON, icon);
+                        mSuggestionHost.notifyPropertyModelsChanged();
+                    });
+        }
+
         if (mOriginalTitle == null) mOriginalTitle = mTabProvider.get().getTitle();
         model.set(EditUrlSuggestionProperties.TITLE_TEXT, mOriginalTitle);
         model.set(EditUrlSuggestionProperties.URL_TEXT, mLastProcessedSuggestion.getUrl());
     }
 
     @Override
-    public void onNativeInitialized() {}
+    public void onNativeInitialized() {
+        mEnableSuggestionFavicons =
+                ChromeFeatureList.isEnabled(ChromeFeatureList.OMNIBOX_SHOW_SUGGESTION_FAVICONS);
+    }
+
+    /**
+     * Updates the profile used for extracting website favicons.
+     * @param profile The profile to be used.
+     */
+    public void setProfile(Profile profile) {
+        if (mCurrentUserProfile == profile) return;
+        mCurrentUserProfile = profile;
+        mLargeIconBridge = null;
+    }
 
     /**
      * @param provider A means of accessing the activity's tab.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionProperties.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionProperties.java
index b4cfc6b..68cbd20 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionProperties.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionProperties.java
@@ -4,6 +4,7 @@
 
 package org.chromium.chrome.browser.omnibox.suggestions.editurl;
 
+import android.graphics.Bitmap;
 import android.view.View;
 
 import org.chromium.chrome.browser.omnibox.suggestions.SuggestionCommonProperties;
@@ -29,8 +30,11 @@
     public static final WritableObjectPropertyKey<View.OnClickListener> TEXT_CLICK_LISTENER =
             new WritableObjectPropertyKey<>();
 
-    private static final PropertyKey[] ALL_UNIQUE_KEYS =
-            new PropertyKey[] {TITLE_TEXT, URL_TEXT, BUTTON_CLICK_LISTENER, TEXT_CLICK_LISTENER};
+    public static final WritableObjectPropertyKey<Bitmap> SITE_FAVICON =
+            new WritableObjectPropertyKey<>();
+
+    private static final PropertyKey[] ALL_UNIQUE_KEYS = new PropertyKey[] {
+            TITLE_TEXT, URL_TEXT, BUTTON_CLICK_LISTENER, TEXT_CLICK_LISTENER, SITE_FAVICON};
 
     public static final PropertyKey[] ALL_KEYS =
             PropertyModel.concatKeys(ALL_UNIQUE_KEYS, SuggestionCommonProperties.ALL_KEYS);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionViewBinder.java
index 04faee3..77a1830 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionViewBinder.java
@@ -4,10 +4,14 @@
 
 package org.chromium.chrome.browser.omnibox.suggestions.editurl;
 
+import android.graphics.Bitmap;
+import android.view.View;
 import android.view.ViewGroup;
+import android.widget.ImageView;
 import android.widget.TextView;
 
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.omnibox.suggestions.SuggestionCommonProperties;
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel;
 
@@ -32,7 +36,27 @@
                             model.get(EditUrlSuggestionProperties.BUTTON_CLICK_LISTENER));
         } else if (EditUrlSuggestionProperties.TEXT_CLICK_LISTENER == propertyKey) {
             view.setOnClickListener(model.get(EditUrlSuggestionProperties.TEXT_CLICK_LISTENER));
+        } else if (SuggestionCommonProperties.SHOW_SUGGESTION_ICONS == propertyKey) {
+            boolean showIcons = model.get(SuggestionCommonProperties.SHOW_SUGGESTION_ICONS);
+            view.findViewById(R.id.edit_url_space)
+                    .setVisibility(showIcons ? View.GONE : View.VISIBLE);
+            view.findViewById(R.id.edit_url_favicon)
+                    .setVisibility(showIcons ? View.VISIBLE : View.GONE);
+            updateSiteFavicon(view.findViewById(R.id.edit_url_favicon), model);
+        } else if (EditUrlSuggestionProperties.SITE_FAVICON == propertyKey) {
+            updateSiteFavicon(view.findViewById(R.id.edit_url_favicon), model);
         }
         // TODO(mdjones): Support SuggestionCommonProperties.*
     }
+
+    private static void updateSiteFavicon(ImageView view, PropertyModel model) {
+        if (!model.get(SuggestionCommonProperties.SHOW_SUGGESTION_ICONS)) return;
+
+        Bitmap bitmap = model.get(EditUrlSuggestionProperties.SITE_FAVICON);
+        if (bitmap != null) {
+            view.setImageBitmap(bitmap);
+        } else {
+            view.setImageResource(R.drawable.ic_globe_24dp);
+        }
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tabgroup/TabGroupModelFilter.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tabgroup/TabGroupModelFilter.java
index 860b12a4..425c2946b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tabgroup/TabGroupModelFilter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tabgroup/TabGroupModelFilter.java
@@ -252,10 +252,7 @@
 
         int destinationGroupId = destinationTab.getRootId();
         List<Tab> tabsToMerge = getRelatedTabList(sourceTabId);
-        int sourceTabIndexInTabModel =
-                TabModelUtils.getTabIndexById(getTabModel(), sourceTab.getId());
         int destinationIndexInTabModel = getTabModelDestinationIndex(destinationTab);
-        boolean isMovingBackward = sourceTabIndexInTabModel < destinationIndexInTabModel;
 
         if (!needToUpdateTabModel(tabsToMerge, destinationIndexInTabModel)) {
             for (int i = 0; i < tabsToMerge.size(); i++) {
@@ -271,13 +268,31 @@
                         tabsToMerge.get(tabsToMerge.size() - 1), group.getLastShownTabId());
             }
         } else {
-            for (int i = 0; i < tabsToMerge.size(); i++) {
-                Tab tab = tabsToMerge.get(i);
-                tab.setRootId(destinationGroupId);
-                getTabModel().moveTab(tab.getId(),
-                        isMovingBackward ? destinationIndexInTabModel
-                                         : destinationIndexInTabModel++);
-            }
+            mergeListOfTabsToGroup(tabsToMerge, destinationTab);
+        }
+    }
+
+    /**
+     * This method appends a list of {@link Tab}s to the destination group that contains the
+     * {@code} destinationTab. The {@link TabModel} ordering of the tabs in the given list is not
+     * preserved. After calling this method, the {@link TabModel} ordering of these tabs would
+     * become the ordering of {@code tabs}.
+     *
+     * @param tabs List of {@link Tab}s to be appended.
+     * @param destinationTab The destination {@link Tab} to be append to.
+     */
+    public void mergeListOfTabsToGroup(List<Tab> tabs, Tab destinationTab) {
+        int destinationGroupId = destinationTab.getRootId();
+        int destinationIndexInTabModel = getTabModelDestinationIndex(destinationTab);
+
+        for (int i = 0; i < tabs.size(); i++) {
+            Tab tab = tabs.get(i);
+            int index = TabModelUtils.getTabIndexById(getTabModel(), tab.getId());
+            boolean isMergingBackward = index < destinationIndexInTabModel;
+
+            tab.setRootId(destinationGroupId);
+            getTabModel().moveTab(tab.getId(),
+                    isMergingBackward ? destinationIndexInTabModel : destinationIndexInTabModel++);
         }
     }
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionTest.java
index 92e14b0..b71fd9e 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionTest.java
@@ -7,6 +7,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.Context;
+import android.content.res.Resources;
 import android.support.test.filters.SmallTest;
 import android.view.View;
 
@@ -39,6 +41,12 @@
     private PropertyModel mModel;
 
     @Mock
+    Context mContext;
+
+    @Mock
+    Resources mResources;
+
+    @Mock
     private ActivityTabProvider mTabProvider;
 
     @Mock
@@ -66,6 +74,7 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
+        when(mContext.getResources()).thenReturn(mResources);
         when(mTab.getUrl()).thenReturn(TEST_URL);
         when(mTab.getTitle()).thenReturn(TEST_TITLE);
         when(mTab.isNativePage()).thenReturn(false);
@@ -81,7 +90,8 @@
 
         mModel = new PropertyModel.Builder(EditUrlSuggestionProperties.ALL_KEYS).build();
 
-        mProcessor = new EditUrlSuggestionProcessor(mLocationBarDelegate, mSelectionHandler);
+        mProcessor = new EditUrlSuggestionProcessor(
+                mContext, null, mLocationBarDelegate, mSelectionHandler);
         mProcessor.setActivityTabProvider(mTabProvider);
 
         when(mEditButton.getId()).thenReturn(R.id.url_edit_icon);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/tasks/tabgroup/TabGroupModelFilterUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/tasks/tabgroup/TabGroupModelFilterUnitTest.java
index 631f40e..14e0819 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/tasks/tabgroup/TabGroupModelFilterUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/tasks/tabgroup/TabGroupModelFilterUnitTest.java
@@ -64,6 +64,8 @@
     private static final int POSITION5 = 4;
     private static final int POSITION6 = 5;
 
+    private static final int NEW_TAB_ID = 159;
+
     @Mock
     TabModel mTabModel;
 
@@ -174,6 +176,13 @@
         doReturn(0).when(mTabModel).index();
     }
 
+    private Tab addTabToTabModel() {
+        Tab tab = prepareTab(NEW_TAB_ID, NEW_TAB_ID);
+        mTabModel.addTab(tab, -1, TabLaunchType.FROM_CHROME_UI);
+        mTabModelObserverCaptor.getValue().didAddTab(tab, TabLaunchType.FROM_CHROME_UI);
+        return tab;
+    }
+
     @Before
     public void setUp() {
         RecordUserAction.setDisabledForTests(true);
@@ -315,6 +324,53 @@
     }
 
     @Test
+    public void mergeListOfTabsToGroup_All_Backward() {
+        List<Tab> expectedTabModel =
+                new ArrayList<>(Arrays.asList(mTab2, mTab3, mTab5, mTab6, mTab1, mTab4));
+        List<Tab> tabsToMerge = new ArrayList<>(Arrays.asList(mTab1, mTab4));
+
+        mTabGroupModelFilter.mergeListOfTabsToGroup(tabsToMerge, mTab5);
+
+        verify(mTabModel).moveTab(mTab1.getId(), POSITION6 + 1);
+        verify(mTabModel).moveTab(mTab4.getId(), POSITION6 + 1);
+        verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab1, mTab5.getId());
+        verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab4, mTab5.getId());
+        assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
+    }
+
+    @Test
+    public void mergeListOfTabsToGroup_All_Forward() {
+        Tab newTab = addTabToTabModel();
+        List<Tab> tabsToMerge = new ArrayList<>(Arrays.asList(mTab4, newTab));
+        List<Tab> expectedTabModel =
+                new ArrayList<>(Arrays.asList(mTab1, mTab4, newTab, mTab2, mTab3, mTab5, mTab6));
+
+        mTabGroupModelFilter.mergeListOfTabsToGroup(tabsToMerge, mTab1);
+
+        verify(mTabModel).moveTab(mTab4.getId(), POSITION1 + 1);
+        verify(mTabModel).moveTab(newTab.getId(), POSITION1 + 2);
+        verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab4, mTab1.getId());
+        verify(mTabGroupModelFilterObserver).didMergeTabToGroup(newTab, mTab1.getId());
+        assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
+    }
+
+    @Test
+    public void mergeListOfTabsToGroup_Any_direction() {
+        Tab newTab = addTabToTabModel();
+        List<Tab> tabsToMerge = new ArrayList<>(Arrays.asList(mTab1, newTab));
+        List<Tab> expectedTabModel =
+                new ArrayList<>(Arrays.asList(mTab2, mTab3, mTab4, mTab1, newTab, mTab5, mTab6));
+
+        mTabGroupModelFilter.mergeListOfTabsToGroup(tabsToMerge, mTab4);
+
+        verify(mTabModel).moveTab(mTab1.getId(), POSITION4 + 1);
+        verify(mTabModel).moveTab(newTab.getId(), POSITION4 + 1);
+        verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab1, mTab4.getId());
+        verify(mTabGroupModelFilterObserver).didMergeTabToGroup(newTab, mTab4.getId());
+        assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
+    }
+
+    @Test
     public void moveGroup_Backward() {
         List<Tab> expectedTabModel =
                 new ArrayList<>(Arrays.asList(mTab1, mTab4, mTab2, mTab3, mTab5, mTab6));
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index e369476..8edaa40 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-77.0.3816.0_rc-r1-merged.afdo.bz2
\ No newline at end of file
+chromeos-chrome-amd64-77.0.3817.0_rc-r1-merged.afdo.bz2
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 1ef8995..bea5fd6 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1510,6 +1510,8 @@
     "sharing/sharing_service.h",
     "sharing/sharing_service_factory.cc",
     "sharing/sharing_service_factory.h",
+    "sharing/sharing_sync_preference.cc",
+    "sharing/sharing_sync_preference.h",
     "shell_integration.cc",
     "shell_integration.h",
     "shell_integration_android.cc",
diff --git a/chrome/browser/android/oom_intervention/near_oom_monitor_unittest.cc b/chrome/browser/android/oom_intervention/near_oom_monitor_unittest.cc
index bf31354..73f7fdf 100644
--- a/chrome/browser/android/oom_intervention/near_oom_monitor_unittest.cc
+++ b/chrome/browser/android/oom_intervention/near_oom_monitor_unittest.cc
@@ -5,8 +5,8 @@
 #include "chrome/browser/android/oom_intervention/near_oom_monitor.h"
 
 #include "base/bind.h"
-#include "base/message_loop/message_loop.h"
 #include "base/sequenced_task_runner.h"
+#include "base/test/scoped_task_environment.h"
 #include "base/test/test_mock_time_task_runner.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -82,7 +82,7 @@
 
  protected:
   scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
-  base::MessageLoop message_loop_;
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
   std::unique_ptr<MockNearOomMonitor> monitor_;
 };
 
diff --git a/chrome/browser/chromeos/accessibility/magnifier_type.h b/chrome/browser/chromeos/accessibility/magnifier_type.h
index 24a79da02..4597fe81 100644
--- a/chrome/browser/chromeos/accessibility/magnifier_type.h
+++ b/chrome/browser/chromeos/accessibility/magnifier_type.h
@@ -11,6 +11,7 @@
 enum MagnifierType {
   MAGNIFIER_DISABLED = 0,  // Used by enterprise policy.
   MAGNIFIER_FULL = 1,
+  MAGNIFIER_DOCKED = 2,
   // Never shipped. Deprioritized in 2013. http://crbug.com/170850
   // MAGNIFIER_PARTIAL = 2,
   // TODO(afakhy): Consider adding Docked Magnifier type (shipped in M66) for
diff --git a/chrome/browser/chromeos/login/enrollment/enrollment_screen_browsertest.cc b/chrome/browser/chromeos/login/enrollment/enrollment_screen_browsertest.cc
index cb27e26b..f042bc4 100644
--- a/chrome/browser/chromeos/login/enrollment/enrollment_screen_browsertest.cc
+++ b/chrome/browser/chromeos/login/enrollment/enrollment_screen_browsertest.cc
@@ -25,9 +25,9 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+using testing::_;
 using testing::InvokeWithoutArgs;
 using testing::Mock;
-using testing::_;
 
 namespace chromeos {
 
@@ -61,7 +61,6 @@
   test::EnrollmentUIMixin enrollment_ui_{&mixin_host_};
 
  private:
-
   DISALLOW_COPY_AND_ASSIGN(EnrollmentScreenTest);
 };
 
@@ -109,25 +108,16 @@
   EnrollmentScreenView* view = enrollment_screen()->GetView();
   ASSERT_TRUE(view);
 
-  test::JSChecker checker(
-      LoginDisplayHost::default_host()->GetOobeWebContents());
-
   // Run through the flow
   view->Show();
   OobeScreenWaiter(EnrollmentScreenView::kScreenId).Wait();
-  checker.ExpectTrue(
-      "window.getComputedStyle(document.getElementById('oauth-enroll-step-"
-      "signin')).display !== 'none'");
+  enrollment_ui_.WaitForStep(test::ui::kEnrollmentStepSignin);
 
   view->ShowEnrollmentSpinnerScreen();
-  checker.ExpectTrue(
-      "window.getComputedStyle(document.getElementById('oauth-enroll-step-"
-      "working')).display !== 'none'");
+  enrollment_ui_.WaitForStep(test::ui::kEnrollmentStepWorking);
 
   view->ShowAttestationBasedEnrollmentSuccessScreen("fake domain");
-  checker.ExpectTrue(
-      "window.getComputedStyle(document.getElementById('oauth-enroll-step-"
-      "success')).display !== 'none'");
+  enrollment_ui_.WaitForStep(test::ui::kEnrollmentStepSuccess);
 }
 
 class ForcedAttestationAuthEnrollmentScreenTest : public EnrollmentScreenTest {
diff --git a/chrome/browser/chromeos/login/enterprise_enrollment_browsertest.cc b/chrome/browser/chromeos/login/enterprise_enrollment_browsertest.cc
index b7baad8e..743f8626 100644
--- a/chrome/browser/chromeos/login/enterprise_enrollment_browsertest.cc
+++ b/chrome/browser/chromeos/login/enterprise_enrollment_browsertest.cc
@@ -7,6 +7,7 @@
 #include "base/json/json_reader.h"
 #include "base/json/string_escape.h"
 #include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/login/enrollment/enrollment_screen.h"
@@ -38,6 +39,7 @@
 
 namespace {
 
+constexpr char kEnrollmentUI[] = "enterprise-enrollment";
 constexpr char kAdDialog[] = "oauth-enroll-ad-join-ui";
 constexpr char kAdErrorCard[] = "oauth-enroll-active-directory-join-error-card";
 
@@ -138,9 +140,7 @@
  public:
   explicit EnterpriseEnrollmentTestBase(bool should_initialize_webui)
       : LoginManagerTest(true /*should_launch_browser*/,
-                         should_initialize_webui) {
-  }
-
+                         should_initialize_webui) {}
 
   // Submits regular enrollment credentials.
   void SubmitEnrollmentCredentials() {
@@ -149,7 +149,6 @@
     ExecutePendingJavaScript();
   }
 
-
   // Completes the enrollment process.
   void CompleteEnrollment() {
     enrollment_screen()->OnDeviceEnrolled();
@@ -211,7 +210,7 @@
   }
 
   std::string AdElement(const std::string& inner_id) {
-    return test::GetOobeElementPath({kAdDialog, inner_id});
+    return test::GetOobeElementPath({kEnrollmentUI, kAdDialog, inner_id});
   }
 
   void ExpectElementValid(const std::string& inner_id, bool is_valid) {
@@ -222,28 +221,33 @@
     EXPECT_TRUE(
         enrollment_ui_.IsStepDisplayed(test::ui::kEnrollmentStepADJoin));
 
-    std::initializer_list<base::StringPiece> ad_credentials{kAdDialog,
-                                                            kAdCredentialsStep};
+    std::initializer_list<base::StringPiece> ad_credentials{
+        kEnrollmentUI, kAdDialog, kAdCredentialsStep};
     test::OobeJS().ExpectVisiblePath(ad_credentials);
     test::OobeJS().ExpectNE(
         test::GetOobeElementPath(ad_credentials) + ".clientWidth", 0);
     test::OobeJS().ExpectNE(
         test::GetOobeElementPath(ad_credentials) + ".clientHeight", 0);
-    test::OobeJS().ExpectHiddenPath({kAdDialog, kAdUnlockConfigurationStep});
+    test::OobeJS().ExpectHiddenPath(
+        {kEnrollmentUI, kAdDialog, kAdUnlockConfigurationStep});
   }
 
   void CheckConfigurationSelectionVisible(bool visible) {
     if (visible)
-      test::OobeJS().ExpectVisiblePath({kAdDialog, kAdJoinConfigurationForm});
+      test::OobeJS().ExpectVisiblePath(
+          {kEnrollmentUI, kAdDialog, kAdJoinConfigurationForm});
     else
-      test::OobeJS().ExpectHiddenPath({kAdDialog, kAdJoinConfigurationForm});
+      test::OobeJS().ExpectHiddenPath(
+          {kEnrollmentUI, kAdDialog, kAdJoinConfigurationForm});
   }
 
   void CheckActiveDirectoryUnlockConfigurationShown() {
     EXPECT_TRUE(
         enrollment_ui_.IsStepDisplayed(test::ui::kEnrollmentStepADJoin));
-    test::OobeJS().ExpectHiddenPath({kAdDialog, kAdCredentialsStep});
-    test::OobeJS().ExpectVisiblePath({kAdDialog, kAdUnlockConfigurationStep});
+    test::OobeJS().ExpectHiddenPath(
+        {kEnrollmentUI, kAdDialog, kAdCredentialsStep});
+    test::OobeJS().ExpectVisiblePath(
+        {kEnrollmentUI, kAdDialog, kAdUnlockConfigurationStep});
   }
 
   void CheckAttributeValue(const base::Value* config_value,
@@ -295,8 +299,9 @@
     for (size_t i = 0; i < options->GetList().size(); ++i) {
       const base::Value& option = options->GetList()[i];
       // Select configuration value.
-      test::OobeJS().SelectElementInPath(std::to_string(i),
-                                         {kAdDialog, kAdConfigurationSelect});
+      test::OobeJS().SelectElementInPath(
+          base::NumberToString(i),
+          {kEnrollmentUI, kAdDialog, kAdConfigurationSelect});
 
       CheckAttributeValue(
           option.FindKeyOfType("name", base::Value::Type::STRING), "",
@@ -331,23 +336,28 @@
                                         const std::string& password) {
     CheckActiveDirectoryCredentialsShown();
 
-    test::OobeJS().TypeIntoPath(machine_name, {kAdDialog, kAdMachineNameInput});
-    test::OobeJS().TypeIntoPath(username, {kAdDialog, kAdUsernameInput});
-    test::OobeJS().TypeIntoPath(password, {kAdDialog, kAdPasswordInput});
-
-    test::OobeJS().TapOnPath({kAdDialog, kAdMoreOptionsButton});
-    test::OobeJS().TypeIntoPath(machine_dn,
-                                {kAdDialog, kAdMachineOrgUnitInput});
+    test::OobeJS().TypeIntoPath(
+        machine_name, {kEnrollmentUI, kAdDialog, kAdMachineNameInput});
+    test::OobeJS().TypeIntoPath(username,
+                                {kEnrollmentUI, kAdDialog, kAdUsernameInput});
+    test::OobeJS().TypeIntoPath(password,
+                                {kEnrollmentUI, kAdDialog, kAdPasswordInput});
+    test::OobeJS().TapOnPath({kEnrollmentUI, kAdDialog, kAdMoreOptionsButton});
+    test::OobeJS().TypeIntoPath(
+        machine_dn, {kEnrollmentUI, kAdDialog, kAdMachineOrgUnitInput});
 
     if (!encryption_types.empty()) {
-      test::OobeJS().SelectElementInPath(encryption_types,
-                                         {kAdDialog, kAdEncryptionTypesSelect});
+      test::OobeJS().SelectElementInPath(
+          encryption_types,
+          {kEnrollmentUI, kAdDialog, kAdEncryptionTypesSelect});
     }
-    test::OobeJS().TapOnPath({kAdDialog, kAdMoreOptionsSaveButton});
+    test::OobeJS().TapOnPath(
+        {kEnrollmentUI, kAdDialog, kAdMoreOptionsSaveButton});
     test::OobeJS()
-        .CreateEnabledWaiter(true /* enabled */, {kAdDialog, kNextButton})
+        .CreateEnabledWaiter(true /* enabled */,
+                             {kEnrollmentUI, kAdDialog, kNextButton})
         ->Wait();
-    test::OobeJS().TapOnPath({kAdDialog, kNextButton});
+    test::OobeJS().TapOnPath({kEnrollmentUI, kAdDialog, kNextButton});
   }
 
   void SetExpectedJoinRequest(
@@ -372,7 +382,6 @@
     mock_auth_policy_client()->set_expected_request(std::move(request));
   }
 
-
   MockAuthPolicyClient* mock_auth_policy_client() {
     return mock_auth_policy_client_;
   }
@@ -645,7 +654,7 @@
                                    "legacy", kAdTestUser, "password");
   WaitForMessage(&message_queue, "\"ShowADJoinError\"");
   enrollment_ui_.WaitForStep(test::ui::kEnrollmentStepADJoinError);
-  test::OobeJS().TapOnPath({kAdErrorCard, kSubmitButton});
+  test::OobeJS().TapOnPath({kEnrollmentUI, kAdErrorCard, kSubmitButton});
   enrollment_ui_.WaitForStep(test::ui::kEnrollmentStepADJoin);
 }
 
@@ -671,22 +680,23 @@
   ExpectElementValid(kAdUnlockPasswordInput, true);
 
   // Test skipping the password step and getting back.
-  test::OobeJS().TapOnPath({kAdDialog, kSkipButton});
+  test::OobeJS().TapOnPath({kEnrollmentUI, kAdDialog, kSkipButton});
   CheckActiveDirectoryCredentialsShown();
   CheckConfigurationSelectionVisible(false);
-  test::OobeJS().TapOnPath({kAdDialog, kAdBackToUnlockButton});
+  test::OobeJS().TapOnPath({kEnrollmentUI, kAdDialog, kAdBackToUnlockButton});
   CheckActiveDirectoryUnlockConfigurationShown();
 
   // Enter wrong unlock password.
-  test::OobeJS().TypeIntoPath("wrong_password",
-                              {kAdDialog, kAdUnlockPasswordInput});
-  test::OobeJS().TapOnPath({kAdDialog, kAdUnlockButton});
+  test::OobeJS().TypeIntoPath(
+      "wrong_password", {kEnrollmentUI, kAdDialog, kAdUnlockPasswordInput});
+  test::OobeJS().TapOnPath({kEnrollmentUI, kAdDialog, kAdUnlockButton});
   WaitForMessage(&message_queue, "\"ShowJoinDomainError\"");
   ExpectElementValid(kAdUnlockPasswordInput, false);
 
   // Enter right unlock password.
-  test::OobeJS().TypeIntoPath("test765!", {kAdDialog, kAdUnlockPasswordInput});
-  test::OobeJS().TapOnPath({kAdDialog, kAdUnlockButton});
+  test::OobeJS().TypeIntoPath(
+      "test765!", {kEnrollmentUI, kAdDialog, kAdUnlockPasswordInput});
+  test::OobeJS().TapOnPath({kEnrollmentUI, kAdDialog, kAdUnlockButton});
   WaitForMessage(&message_queue, "\"SetAdJoinConfiguration\"");
   CheckActiveDirectoryCredentialsShown();
   // Configuration selector should be visible.
diff --git a/chrome/browser/chromeos/login/saml/saml_browsertest.cc b/chrome/browser/chromeos/login/saml/saml_browsertest.cc
index 3402848..690bc27 100644
--- a/chrome/browser/chromeos/login/saml/saml_browsertest.cc
+++ b/chrome/browser/chromeos/login/saml/saml_browsertest.cc
@@ -834,7 +834,7 @@
   guest_view::GuestViewManager::set_factory_for_testing(
       &guest_view_manager_factory_);
   gaia_frame_parent_ = "oauth-enroll-auth-view";
-  authenticator_id_ = "$('oauth-enrollment').authenticator_";
+  authenticator_id_ = "$('enterprise-enrollment').authenticator_";
 }
 
 SAMLEnrollmentTest::~SAMLEnrollmentTest() {}
diff --git a/chrome/browser/chromeos/login/saml/saml_password_expiry_notification.cc b/chrome/browser/chromeos/login/saml/saml_password_expiry_notification.cc
index cad3e28..bc28e4b 100644
--- a/chrome/browser/chromeos/login/saml/saml_password_expiry_notification.cc
+++ b/chrome/browser/chromeos/login/saml/saml_password_expiry_notification.cc
@@ -31,6 +31,7 @@
 #include "ui/message_center/public/cpp/notification.h"
 #include "ui/message_center/public/cpp/notification_delegate.h"
 
+using message_center::ButtonInfo;
 using message_center::HandleNotificationClickDelegate;
 using message_center::Notification;
 using message_center::NotificationObserver;
@@ -53,7 +54,7 @@
     message_center::NotifierType::SYSTEM_COMPONENT,
     kNotificationId);
 
-// Simplest type of notification - has text but no other UI elements.
+// Simplest type of notification UI - no progress bars, images etc.
 const NotificationType kNotificationType =
     message_center::NOTIFICATION_TYPE_SIMPLE;
 
@@ -64,34 +65,41 @@
 // The icon to use for this notification - looks like an office building.
 const gfx::VectorIcon& kIcon = vector_icons::kBusinessIcon;
 
-// Warning level NORMAL means the notification heading is blue.
-const SystemNotificationWarningLevel kWarningLevel =
-    SystemNotificationWarningLevel::NORMAL;
-
 // Leaving this empty means the notification is attributed to the system -
 // ie "Chromium OS" or similar.
-const base::NoDestructor<base::string16> kDisplaySource;
+const base::NoDestructor<base::string16> kEmptyDisplaySource;
 
 // No origin URL is needed since the notification comes from the system.
 const base::NoDestructor<GURL> kEmptyOriginUrl;
 
-// Line separator in the notification body.
-const base::NoDestructor<base::string16> kLineSeparator(
-    base::string16(1, '\n'));
+// When the password will expire in |kCriticalWarningDays| or less, the warning
+// will have red text, slightly stronger language, and doesn't automatically
+// time out - it stays on the screen until the user hides or dismisses it.
+const int kCriticalWarningDays = 3;
 
 base::string16 GetTitleText(int less_than_n_days) {
-  const bool hasExpired = (less_than_n_days <= 0);
-  return hasExpired ? l10n_util::GetStringUTF16(IDS_PASSWORD_HAS_EXPIRED_TITLE)
-                    : l10n_util::GetStringUTF16(IDS_PASSWORD_WILL_EXPIRE_TITLE);
+  return l10n_util::GetPluralStringFUTF16(IDS_PASSWORD_EXPIRY_DAYS_BODY,
+                                          less_than_n_days);
 }
 
-base::string16 GetBodyText(int less_than_n_days) {
-  const std::vector<base::string16> body_lines = {
-      l10n_util::GetPluralStringFUTF16(IDS_PASSWORD_EXPIRY_DAYS_BODY,
-                                       std::max(less_than_n_days, 0)),
-      l10n_util::GetStringUTF16(IDS_PASSWORD_EXPIRY_CHOOSE_NEW_PASSWORD_LINK)};
+base::string16 GetBodyText(bool is_critical) {
+  return is_critical
+             ? l10n_util::GetStringUTF16(
+                   IDS_PASSWORD_EXPIRY_CALL_TO_ACTION_CRITICAL)
+             : l10n_util::GetStringUTF16(IDS_PASSWORD_EXPIRY_CALL_TO_ACTION);
+}
 
-  return base::JoinString(body_lines, *kLineSeparator);
+RichNotificationData GetRichNotificationData(bool is_critical) {
+  RichNotificationData result;
+  result.never_timeout = is_critical;
+  result.buttons = std::vector<ButtonInfo>{ButtonInfo(
+      l10n_util::GetStringUTF16(IDS_PASSWORD_EXPIRY_CHANGE_PASSWORD_BUTTON))};
+  return result;
+}
+
+SystemNotificationWarningLevel GetWarningLevel(bool is_critical) {
+  return is_critical ? SystemNotificationWarningLevel::CRITICAL_WARNING
+                     : SystemNotificationWarningLevel::WARNING;
 }
 
 void ShowNotificationImpl(
@@ -100,15 +108,18 @@
     scoped_refptr<message_center::NotificationDelegate> delegate) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
+  const bool is_critical = less_than_n_days <= kCriticalWarningDays;
   const base::string16 title = GetTitleText(less_than_n_days);
-  const base::string16 body = GetBodyText(less_than_n_days);
+  const base::string16 body = GetBodyText(is_critical);
+  const RichNotificationData rich_notification_data =
+      GetRichNotificationData(is_critical);
+  const SystemNotificationWarningLevel warning_level =
+      GetWarningLevel(is_critical);
 
-  // TODO(olsen): Add button to notification - see UI mock.
-  RichNotificationData rich_notification_data;
   std::unique_ptr<Notification> notification = ash::CreateSystemNotification(
-      kNotificationType, kNotificationId, title, body, *kDisplaySource,
+      kNotificationType, kNotificationId, title, body, *kEmptyDisplaySource,
       *kEmptyOriginUrl, *kNotifierId, rich_notification_data, delegate, kIcon,
-      kWarningLevel);
+      warning_level);
 
   NotificationDisplayService* nds =
       NotificationDisplayServiceFactory::GetForProfile(profile);
@@ -278,8 +289,10 @@
 
 void Rechecker::Click(const base::Optional<int>& button_index,
                       const base::Optional<base::string16>& reply) {
-  // TODO(olsen): Add a button, only handle clicks on the button itself.
-  PasswordChangeDialog::Show(profile_);
+  bool clicked_on_button = button_index.has_value();
+  if (clicked_on_button) {
+    PasswordChangeDialog::Show(profile_);
+  }
 }
 
 void Rechecker::Close(bool by_user) {
diff --git a/chrome/browser/chromeos/login/saml/saml_password_expiry_notification_unittest.cc b/chrome/browser/chromeos/login/saml/saml_password_expiry_notification_unittest.cc
index e6194499..b247e5d 100644
--- a/chrome/browser/chromeos/login/saml/saml_password_expiry_notification_unittest.cc
+++ b/chrome/browser/chromeos/login/saml/saml_password_expiry_notification_unittest.cc
@@ -109,9 +109,9 @@
   ShowSamlPasswordExpiryNotification(profile_, 0);
   ASSERT_TRUE(Notification().has_value());
 
-  EXPECT_EQ(utf16("Password has expired"), Notification()->title());
-  EXPECT_EQ(utf16("Your current password has expired!\n"
-                  "Click here to choose a new password"),
+  EXPECT_EQ(utf16("Your current password has expired!"),
+            Notification()->title());
+  EXPECT_EQ(utf16("Please choose a new password immediately"),
             Notification()->message());
 
   DismissSamlPasswordExpiryNotification(profile_);
@@ -122,9 +122,9 @@
   ShowSamlPasswordExpiryNotification(profile_, 14);
   ASSERT_TRUE(Notification().has_value());
 
-  EXPECT_EQ(utf16("Password will soon expire"), Notification()->title());
-  EXPECT_EQ(utf16("Your current password will expire in less than 14 days!\n"
-                  "Click here to choose a new password"),
+  EXPECT_EQ(utf16("Your current password will expire in less than 14 days!"),
+            Notification()->title());
+  EXPECT_EQ(utf16("Please choose a new password now"),
             Notification()->message());
 
   DismissSamlPasswordExpiryNotification(profile_);
@@ -156,29 +156,32 @@
 
   // Notification is shown immediately since password has expired.
   EXPECT_TRUE(Notification().has_value());
-  EXPECT_EQ(utf16("Password has expired"), Notification()->title());
+  EXPECT_EQ(utf16("Your current password has expired!"),
+            Notification()->title());
 }
 
 TEST_F(SamlPasswordExpiryNotificationTest, MaybeShow_WillSoonExpire) {
-  SetExpirationTime(base::Time::Now() + (kAdvanceWarningTime / 2));
+  SetExpirationTime(base::Time::Now() + (kAdvanceWarningTime / 2) - kOneHour);
   MaybeShowSamlPasswordExpiryNotification(profile_);
 
   // Notification is shown immediately since password will soon expire.
   EXPECT_TRUE(Notification().has_value());
-  EXPECT_EQ(utf16("Password will soon expire"), Notification()->title());
+  EXPECT_EQ(utf16("Your current password will expire in less than 7 days!"),
+            Notification()->title());
 }
 
 TEST_F(SamlPasswordExpiryNotificationTest, MaybeShow_WillEventuallyExpire) {
-  SetExpirationTime(base::Time::Now() + kOneYear + (kAdvanceWarningTime / 2));
+  SetExpirationTime(base::Time::Now() + kOneYear + kAdvanceWarningTime);
   MaybeShowSamlPasswordExpiryNotification(profile_);
 
   // Notification is not shown when expiration is still over a year away.
   EXPECT_FALSE(Notification().has_value());
 
   // But, it will be shown once we are in the advance warning window:
-  test_environment_.FastForwardBy(kOneYear);
+  test_environment_.FastForwardBy(kOneYear + kOneHour);
   EXPECT_TRUE(Notification().has_value());
-  EXPECT_EQ(utf16("Password will soon expire"), Notification()->title());
+  EXPECT_EQ(utf16("Your current password will expire in less than 14 days!"),
+            Notification()->title());
 }
 
 TEST_F(SamlPasswordExpiryNotificationTest, MaybeShow_DeleteExpirationTime) {
@@ -195,12 +198,13 @@
 }
 
 TEST_F(SamlPasswordExpiryNotificationTest, MaybeShow_PasswordChanged) {
-  SetExpirationTime(base::Time::Now() + (kAdvanceWarningTime / 2));
+  SetExpirationTime(base::Time::Now() + (kAdvanceWarningTime / 2) - kOneHour);
   MaybeShowSamlPasswordExpiryNotification(profile_);
 
   // Notification is shown immediately since password will soon expire.
   EXPECT_TRUE(Notification().has_value());
-  EXPECT_EQ(utf16("Password will soon expire"), Notification()->title());
+  EXPECT_EQ(utf16("Your current password will expire in less than 7 days!"),
+            Notification()->title());
 
   // Password is changed and notification is dismissed.
   SamlPasswordAttributes::DeleteFromPrefs(profile_->GetPrefs());
@@ -231,45 +235,44 @@
 }
 
 TEST_F(SamlPasswordExpiryNotificationTest, TimePasses_NoUserActionTaken) {
-  SetExpirationTime(base::Time::Now() + kOneYear + kAdvanceWarningTime +
-                    (kOneDay / 2));
+  SetExpirationTime(base::Time::Now() + kOneYear + kAdvanceWarningTime);
   MaybeShowSamlPasswordExpiryNotification(profile_);
 
   // Notification is not shown immediately.
   EXPECT_FALSE(Notification().has_value());
 
   // After one year, we are still not quite inside the advance warning window.
-  test_environment_.FastForwardBy(kOneYear);
+  test_environment_.FastForwardBy(kOneYear - (kOneDay / 2));
   EXPECT_FALSE(Notification().has_value());
 
   // But the next day, the notification is shown.
   test_environment_.FastForwardBy(kOneDay);
   EXPECT_TRUE(Notification().has_value());
-  EXPECT_EQ(utf16("Password will soon expire"), Notification()->title());
-  EXPECT_EQ(utf16("Your current password will expire in less than 14 days!\n"
-                  "Click here to choose a new password"),
+  EXPECT_EQ(utf16("Your current password will expire in less than 14 days!"),
+            Notification()->title());
+  EXPECT_EQ(utf16("Please choose a new password now"),
             Notification()->message());
 
   // As time passes, the notification updates each day.
   test_environment_.FastForwardBy(kAdvanceWarningTime / 2);
   EXPECT_TRUE(Notification().has_value());
-  EXPECT_EQ(utf16("Password will soon expire"), Notification()->title());
-  EXPECT_EQ(utf16("Your current password will expire in less than 7 days!\n"
-                  "Click here to choose a new password"),
+  EXPECT_EQ(utf16("Your current password will expire in less than 7 days!"),
+            Notification()->title());
+  EXPECT_EQ(utf16("Please choose a new password now"),
             Notification()->message());
 
   test_environment_.FastForwardBy(kAdvanceWarningTime / 2);
   EXPECT_TRUE(Notification().has_value());
-  EXPECT_EQ(utf16("Password has expired"), Notification()->title());
-  EXPECT_EQ(utf16("Your current password has expired!\n"
-                  "Click here to choose a new password"),
+  EXPECT_EQ(utf16("Your current password has expired!"),
+            Notification()->title());
+  EXPECT_EQ(utf16("Please choose a new password immediately"),
             Notification()->message());
 
   test_environment_.FastForwardBy(kOneYear);
   EXPECT_TRUE(Notification().has_value());
-  EXPECT_EQ(utf16("Password has expired"), Notification()->title());
-  EXPECT_EQ(utf16("Your current password has expired!\n"
-                  "Click here to choose a new password"),
+  EXPECT_EQ(utf16("Your current password has expired!"),
+            Notification()->title());
+  EXPECT_EQ(utf16("Please choose a new password immediately"),
             Notification()->message());
 }
 
diff --git a/chrome/browser/chromeos/login/test/enrollment_ui_mixin.cc b/chrome/browser/chromeos/login/test/enrollment_ui_mixin.cc
index 7a1d6f4..fcd7be8 100644
--- a/chrome/browser/chromeos/login/test/enrollment_ui_mixin.cc
+++ b/chrome/browser/chromeos/login/test/enrollment_ui_mixin.cc
@@ -51,16 +51,18 @@
                                  ui::kEnrollmentStepDeviceAttributesError};
 
 std::string StepVisibleExpression(const std::string& step) {
-  return "document.getElementsByClassName('oauth-enroll-state-" + step +
-         "').length > 0";
+  return "Polymer.dom($('enterprise-enrollment').root)."
+         "querySelectorAll('.oauth-enroll-state-" +
+         step + "').length > 0";
 }
 
 const std::initializer_list<base::StringPiece> kEnrollmentErrorRetryButtonPath =
-    {"oauth-enroll-error-card", "submitButton"};
+    {"enterprise-enrollment", "oauth-enroll-error-card", "submitButton"};
 
 const std::initializer_list<base::StringPiece>
     kEnrollmentDeviceAttributesErrorButtonPath = {
-        "oauth-enroll-attribute-prompt-error-card", "submitButton"};
+        "enterprise-enrollment", "oauth-enroll-attribute-prompt-error-card",
+        "submitButton"};
 
 }  // namespace
 
@@ -85,18 +87,19 @@
 
 void EnrollmentUIMixin::SelectEnrollmentLicense(
     const std::string& license_type) {
-  OobeJS().SelectRadioPath(
-      {"oauth-enroll-license-ui", "license-option-" + license_type});
+  OobeJS().SelectRadioPath({"enterprise-enrollment", "oauth-enroll-license-ui",
+                            "license-option-" + license_type});
 }
 
 void EnrollmentUIMixin::UseSelectedLicense() {
-  OobeJS().TapOnPath({"oauth-enroll-license-ui", "next"});
+  OobeJS().TapOnPath(
+      {"enterprise-enrollment", "oauth-enroll-license-ui", "next"});
 }
 
 void EnrollmentUIMixin::ExpectErrorMessage(int error_message_id,
                                            bool can_retry) {
   const std::string element_path =
-      GetOobeElementPath({"oauth-enroll-error-card"});
+      GetOobeElementPath({"enterprise-enrollment", "oauth-enroll-error-card"});
   const std::string message = OobeJS().GetString(element_path + ".textContent");
   ASSERT_TRUE(std::string::npos !=
               message.find(l10n_util::GetStringUTF8(error_message_id)));
@@ -118,9 +121,12 @@
 
 void EnrollmentUIMixin::SubmitDeviceAttributes(const std::string& asset_id,
                                                const std::string& location) {
-  OobeJS().TypeIntoPath(asset_id, {"oauth-enroll-asset-id"});
-  OobeJS().TypeIntoPath(location, {"oauth-enroll-location"});
-  OobeJS().TapOn("enroll-attributes-submit-button");
+  OobeJS().TypeIntoPath(asset_id,
+                        {"enterprise-enrollment", "oauth-enroll-asset-id"});
+  OobeJS().TypeIntoPath(location,
+                        {"enterprise-enrollment", "oauth-enroll-location"});
+  OobeJS().TapOnPath(
+      {"enterprise-enrollment", "enroll-attributes-submit-button"});
 }
 
 void EnrollmentUIMixin::SetExitHandler() {
diff --git a/chrome/browser/chromeos/login/test/enrollment_ui_mixin.h b/chrome/browser/chromeos/login/test/enrollment_ui_mixin.h
index f27102f6..d6986d9b 100644
--- a/chrome/browser/chromeos/login/test/enrollment_ui_mixin.h
+++ b/chrome/browser/chromeos/login/test/enrollment_ui_mixin.h
@@ -20,6 +20,7 @@
 //  WaitForStep(...) constants.
 
 extern const char kEnrollmentStepSignin[];
+extern const char kEnrollmentStepWorking[];
 extern const char kEnrollmentStepLicenses[];
 extern const char kEnrollmentStepDeviceAttributes[];
 extern const char kEnrollmentStepSuccess[];
diff --git a/chrome/browser/chromeos/login/test/oobe_auth_page_waiter.cc b/chrome/browser/chromeos/login/test/oobe_auth_page_waiter.cc
index 0d5bb49a..b8374e91 100644
--- a/chrome/browser/chromeos/login/test/oobe_auth_page_waiter.cc
+++ b/chrome/browser/chromeos/login/test/oobe_auth_page_waiter.cc
@@ -16,7 +16,7 @@
 
 constexpr char kGaiaAuthenticator[] = "$('gaia-signin').authenticator_";
 constexpr char kEnrollmentAuthenticator[] =
-    "$('oauth-enrollment').authenticator_";
+    "$('enterprise-enrollment').authenticator_";
 
 void MaybeWaitForOobeToInitialize() {
   base::RunLoop run_loop;
diff --git a/chrome/browser/chromeos/policy/affiliated_cloud_policy_invalidator.cc b/chrome/browser/chromeos/policy/affiliated_cloud_policy_invalidator.cc
index b3dea73..5bc3674 100644
--- a/chrome/browser/chromeos/policy/affiliated_cloud_policy_invalidator.cc
+++ b/chrome/browser/chromeos/policy/affiliated_cloud_policy_invalidator.cc
@@ -15,8 +15,10 @@
 AffiliatedCloudPolicyInvalidator::AffiliatedCloudPolicyInvalidator(
     enterprise_management::DeviceRegisterRequest::Type type,
     CloudPolicyCore* core,
-    AffiliatedInvalidationServiceProvider* invalidation_service_provider)
-    : type_(type),
+    AffiliatedInvalidationServiceProvider* invalidation_service_provider,
+    bool is_fcm_enabled)
+    : is_fcm_enabled_(is_fcm_enabled),
+      type_(type),
       core_(core),
       invalidation_service_provider_(invalidation_service_provider),
       highest_handled_invalidation_version_(0) {
@@ -45,8 +47,8 @@
   DCHECK(!invalidator_);
   invalidator_.reset(new CloudPolicyInvalidator(
       type_, core_, base::ThreadTaskRunnerHandle::Get(),
-      base::DefaultClock::GetInstance(),
-      highest_handled_invalidation_version_));
+      base::DefaultClock::GetInstance(), highest_handled_invalidation_version_,
+      is_fcm_enabled_));
   invalidator_->Initialize(invalidation_service);
 }
 
diff --git a/chrome/browser/chromeos/policy/affiliated_cloud_policy_invalidator.h b/chrome/browser/chromeos/policy/affiliated_cloud_policy_invalidator.h
index b07a54f2..611ba9c 100644
--- a/chrome/browser/chromeos/policy/affiliated_cloud_policy_invalidator.h
+++ b/chrome/browser/chromeos/policy/affiliated_cloud_policy_invalidator.h
@@ -36,7 +36,8 @@
   AffiliatedCloudPolicyInvalidator(
       enterprise_management::DeviceRegisterRequest::Type type,
       CloudPolicyCore* core,
-      AffiliatedInvalidationServiceProvider* invalidation_service_provider);
+      AffiliatedInvalidationServiceProvider* invalidation_service_provider,
+      bool is_fcm_enabled);
   ~AffiliatedCloudPolicyInvalidator() override;
 
   // AffiliatedInvalidationServiceProvider::Consumer:
@@ -53,6 +54,10 @@
   // Destroy the current |CloudPolicyInvalidator|, if any.
   void DestroyInvalidator();
 
+  // Whether or not should use FCM (Firebase Cloud Messaging) topic for
+  // registration.
+  const bool is_fcm_enabled_;
+
   const enterprise_management::DeviceRegisterRequest::Type type_;
   CloudPolicyCore* const core_;
 
diff --git a/chrome/browser/chromeos/policy/affiliated_cloud_policy_invalidator_unittest.cc b/chrome/browser/chromeos/policy/affiliated_cloud_policy_invalidator_unittest.cc
index 9bf16b6..d434e66 100644
--- a/chrome/browser/chromeos/policy/affiliated_cloud_policy_invalidator_unittest.cc
+++ b/chrome/browser/chromeos/policy/affiliated_cloud_policy_invalidator_unittest.cc
@@ -16,6 +16,7 @@
 #include "chrome/browser/policy/cloud/cloud_policy_invalidator.h"
 #include "components/invalidation/impl/fake_invalidation_service.h"
 #include "components/invalidation/public/invalidation.h"
+#include "components/invalidation/public/invalidation_util.h"
 #include "components/invalidation/public/object_id_invalidation_map.h"
 #include "components/policy/core/common/cloud/cloud_policy_constants.h"
 #include "components/policy/core/common/cloud/cloud_policy_core.h"
@@ -40,6 +41,7 @@
 
 const int kInvalidationSource = 123;
 const char kInvalidationName[] = "invalidation";
+const char kPolicyInvalidationTopic[] = "policy_invalidation_topic";
 
 class FakeCloudPolicyStore : public CloudPolicyStore {
  public:
@@ -68,11 +70,17 @@
 
 }  // namespace
 
+// Accepts boolean param is_fcm_enabled.
+// true if FCM (Firebase Cloud Messaging) is enabled,
+// and false otherwise.
+class AffiliatedCloudPolicyInvalidatorTest
+    : public testing::TestWithParam<bool> {};
+
 // Verifies that an invalidator is created/destroyed as an invalidation service
 // becomes available/unavailable. Also verifies that invalidations are handled
 // correctly and the highest handled invalidation version is preserved when
 // switching invalidation services.
-TEST(AffiliatedCloudPolicyInvalidatorTest, CreateUseDestroy) {
+TEST_P(AffiliatedCloudPolicyInvalidatorTest, CreateUseDestroy) {
   content::TestBrowserThreadBundle thread_bundle;
 
   // Set up a CloudPolicyCore backed by a simple CloudPolicyStore that does no
@@ -98,16 +106,26 @@
   core.StartRefreshScheduler();
 
   DevicePolicyBuilder policy;
-  policy.policy_data().set_invalidation_source(kInvalidationSource);
-  policy.policy_data().set_invalidation_name(kInvalidationName);
+
+  const bool is_fcm_enabled = GetParam();
+  if (is_fcm_enabled) {
+    // Pass deprecated source if FCM (Firebase Cloud Messaging) is enabled,
+    // because server does not support source field with FCM and
+    // InvalidationService fills the source field with kDeprecatedSourceForFCM.
+    policy.policy_data().set_invalidation_source(
+        syncer::kDeprecatedSourceForFCM);
+    policy.policy_data().set_policy_invalidation_topic(
+        kPolicyInvalidationTopic);
+  } else {
+    policy.policy_data().set_invalidation_source(kInvalidationSource);
+    policy.policy_data().set_invalidation_name(kInvalidationName);
+  }
   policy.Build();
   store.Store(policy.policy());
 
   FakeAffiliatedInvalidationServiceProvider provider;
   AffiliatedCloudPolicyInvalidator affiliated_invalidator(
-      em::DeviceRegisterRequest::DEVICE,
-      &core,
-      &provider);
+      em::DeviceRegisterRequest::DEVICE, &core, &provider, is_fcm_enabled);
 
   // Verify that no invalidator exists initially.
   EXPECT_FALSE(affiliated_invalidator.GetInvalidatorForTest());
@@ -130,10 +148,15 @@
   // timestamp in microseconds. The policy blob contains a timestamp in
   // milliseconds. Convert from one to the other by multiplying by 1000.
   const int64_t invalidation_version = policy.policy_data().timestamp() * 1000;
-  syncer::Invalidation invalidation = syncer::Invalidation::Init(
-      invalidation::ObjectId(kInvalidationSource, kInvalidationName),
-      invalidation_version,
-      "dummy payload");
+  syncer::Invalidation invalidation =
+      is_fcm_enabled
+          ? syncer::Invalidation::Init(
+                invalidation::ObjectId(syncer::kDeprecatedSourceForFCM,
+                                       kPolicyInvalidationTopic),
+                invalidation_version, "dummy payload")
+          : syncer::Invalidation::Init(
+                invalidation::ObjectId(kInvalidationSource, kInvalidationName),
+                invalidation_version, "dummy payload");
   syncer::ObjectIdInvalidationMap invalidation_map;
   invalidation_map.Insert(invalidation);
   invalidator->OnIncomingInvalidation(invalidation_map);
@@ -197,4 +220,8 @@
   affiliated_invalidator.OnInvalidationServiceSet(nullptr);
 }
 
+INSTANTIATE_TEST_SUITE_P(FCMEnabledAndFCMDisabled,
+                         AffiliatedCloudPolicyInvalidatorTest,
+                         testing::Bool() /* is_fcm_enabled */);
+
 }  // namespace policy
diff --git a/chrome/browser/chromeos/policy/affiliated_invalidation_service_provider_impl.cc b/chrome/browser/chromeos/policy/affiliated_invalidation_service_provider_impl.cc
index 403e179..b3e1d30 100644
--- a/chrome/browser/chromeos/policy/affiliated_invalidation_service_provider_impl.cc
+++ b/chrome/browser/chromeos/policy/affiliated_invalidation_service_provider_impl.cc
@@ -19,9 +19,12 @@
 #include "chrome/browser/chromeos/settings/device_identity_provider.h"
 #include "chrome/browser/chromeos/settings/device_oauth2_token_service_factory.h"
 #include "chrome/browser/invalidation/deprecated_profile_invalidation_provider_factory.h"
+#include "chrome/browser/invalidation/profile_invalidation_provider_factory.h"
 #include "chrome/browser/net/system_network_context_manager.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
+#include "components/gcm_driver/instance_id/instance_id_driver.h"
+#include "components/invalidation/impl/fcm_invalidation_service.h"
 #include "components/invalidation/impl/invalidation_state_tracker.h"
 #include "components/invalidation/impl/invalidator_storage.h"
 #include "components/invalidation/impl/profile_invalidation_provider.h"
@@ -37,6 +40,7 @@
 #include "content/public/browser/network_service_instance.h"
 #include "content/public/browser/notification_details.h"
 #include "content/public/browser/notification_service.h"
+#include "services/data_decoder/public/cpp/safe_json_parser.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/mojom/network_context.mojom.h"
 
@@ -44,6 +48,17 @@
 
 namespace {
 
+invalidation::ProfileInvalidationProvider* GetInvalidationProvider(
+    Profile* profile,
+    bool is_fcm_enabled) {
+  if (is_fcm_enabled) {
+    return invalidation::ProfileInvalidationProviderFactory::GetForProfile(
+        profile);
+  }
+  return invalidation::DeprecatedProfileInvalidationProviderFactory::
+      GetForProfile(profile);
+}
+
 // Runs on UI thread.
 void RequestProxyResolvingSocketFactoryOnUIThread(
     base::WeakPtr<invalidation::TiclInvalidationService> owner,
@@ -154,13 +169,17 @@
 }
 
 AffiliatedInvalidationServiceProviderImpl::
-AffiliatedInvalidationServiceProviderImpl()
+    AffiliatedInvalidationServiceProviderImpl(bool is_fcm_enabled,
+                                              std::string fcm_sender_id)
     : invalidation_service_(nullptr),
       consumer_count_(0),
-      is_shut_down_(false) {
+      is_shut_down_(false),
+      is_fcm_enabled_(is_fcm_enabled),
+      fcm_sender_id_(std::move(fcm_sender_id)) {
   // The AffiliatedInvalidationServiceProviderImpl should be created before any
   // user Profiles.
   DCHECK(g_browser_process->profile_manager()->GetLoadedProfiles().empty());
+  DCHECK_EQ(is_fcm_enabled_, !fcm_sender_id_.empty());
 
   // Subscribe to notification about new user profiles becoming available.
   registrar_.Add(this,
@@ -182,8 +201,7 @@
   DCHECK(!is_shut_down_);
   Profile* profile = content::Details<Profile>(details).ptr();
   invalidation::ProfileInvalidationProvider* invalidation_provider =
-      invalidation::DeprecatedProfileInvalidationProviderFactory::GetForProfile(
-          profile);
+      GetInvalidationProvider(profile, is_fcm_enabled_);
   if (!invalidation_provider) {
     // If the Profile does not support invalidation (e.g. guest, incognito),
     // ignore it.
@@ -198,8 +216,14 @@
   }
 
   // Create a state observer for the user's invalidation service.
-  invalidation::InvalidationService* invalidation_service =
-      invalidation_provider->GetInvalidationService();
+  invalidation::InvalidationService* invalidation_service;
+  if (is_fcm_enabled_) {
+    invalidation_service =
+        invalidation_provider->GetInvalidationServiceForCustomSender(
+            fcm_sender_id_);
+  } else {
+    invalidation_service = invalidation_provider->GetInvalidationService();
+  }
   profile_invalidation_service_observers_.push_back(
       std::make_unique<InvalidationServiceObserver>(this,
                                                     invalidation_service));
@@ -255,9 +279,9 @@
   DestroyDeviceInvalidationService();
 }
 
-invalidation::TiclInvalidationService*
-AffiliatedInvalidationServiceProviderImpl::
-    GetDeviceInvalidationServiceForTest() const {
+invalidation::InvalidationService*
+AffiliatedInvalidationServiceProviderImpl::GetDeviceInvalidationServiceForTest()
+    const {
   return device_invalidation_service_.get();
 }
 
@@ -334,32 +358,9 @@
   }
 
   if (!device_invalidation_service_) {
-    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory;
-    if (g_browser_process->system_network_context_manager()) {
-      // system_network_context_manager() can be null during unit tests.
-      url_loader_factory = g_browser_process->system_network_context_manager()
-                               ->GetSharedURLLoaderFactory();
-    }
-
-    identity_provider_ = std::make_unique<chromeos::DeviceIdentityProvider>(
-        chromeos::DeviceOAuth2TokenServiceFactory::Get());
-
-    DCHECK(identity_provider_);
     // If no other connected invalidation service was found and no device-global
     // invalidation service exists, create one.
-    device_invalidation_service_ =
-        std::make_unique<invalidation::TiclInvalidationService>(
-            GetUserAgent(), identity_provider_.get(),
-            g_browser_process->gcm_driver(),
-            base::BindRepeating(&RequestProxyResolvingSocketFactory),
-            base::CreateSingleThreadTaskRunnerWithTraits(
-                {content::BrowserThread::IO}),
-            std::move(url_loader_factory),
-            content::GetNetworkConnectionTracker());
-    device_invalidation_service_->Init(
-        std::unique_ptr<syncer::InvalidationStateTracker>(
-            new invalidation::InvalidatorStorage(
-                g_browser_process->local_state())));
+    device_invalidation_service_ = InitializeDeviceInvalidationService();
     device_invalidation_service_observer_.reset(
         new InvalidationServiceObserver(
                 this,
@@ -386,7 +387,56 @@
 AffiliatedInvalidationServiceProviderImpl::DestroyDeviceInvalidationService() {
   device_invalidation_service_observer_.reset();
   device_invalidation_service_.reset();
-  identity_provider_.reset();
+  device_identity_provider_.reset();
+  device_instance_id_driver_.reset();
+}
+
+std::unique_ptr<invalidation::InvalidationService>
+AffiliatedInvalidationServiceProviderImpl::
+    InitializeDeviceInvalidationService() {
+  scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory;
+  if (g_browser_process->system_network_context_manager()) {
+    // system_network_context_manager() can be null during unit tests.
+    url_loader_factory = g_browser_process->system_network_context_manager()
+                             ->GetSharedURLLoaderFactory();
+    DCHECK(url_loader_factory);
+  }
+
+  device_identity_provider_ =
+      std::make_unique<chromeos::DeviceIdentityProvider>(
+          chromeos::DeviceOAuth2TokenServiceFactory::Get());
+
+  if (is_fcm_enabled_) {
+    device_instance_id_driver_ =
+        std::make_unique<instance_id::InstanceIDDriver>(
+            g_browser_process->gcm_driver());
+    DCHECK(device_instance_id_driver_);
+    auto device_invalidation_service =
+        std::make_unique<invalidation::FCMInvalidationService>(
+            device_identity_provider_.get(), g_browser_process->gcm_driver(),
+            device_instance_id_driver_.get(), g_browser_process->local_state(),
+            base::BindRepeating(
+                data_decoder::SafeJsonParser::Parse,
+                content::ServiceManagerConnection::GetForProcess()
+                    ->GetConnector()),
+            url_loader_factory.get(), fcm_sender_id_);
+    device_invalidation_service->Init();
+    return device_invalidation_service;
+  }
+  auto device_invalidation_service =
+      std::make_unique<invalidation::TiclInvalidationService>(
+          GetUserAgent(), device_identity_provider_.get(),
+          g_browser_process->gcm_driver(),
+          base::BindRepeating(&RequestProxyResolvingSocketFactory),
+          base::CreateSingleThreadTaskRunnerWithTraits(
+              {content::BrowserThread::IO}),
+          std::move(url_loader_factory),
+          content::GetNetworkConnectionTracker());
+
+  device_invalidation_service->Init(
+      std::make_unique<invalidation::InvalidatorStorage>(
+          g_browser_process->local_state()));
+  return device_invalidation_service;
 }
 
 }  // namespace policy
diff --git a/chrome/browser/chromeos/policy/affiliated_invalidation_service_provider_impl.h b/chrome/browser/chromeos/policy/affiliated_invalidation_service_provider_impl.h
index 6cf803c..d9ed2b26 100644
--- a/chrome/browser/chromeos/policy/affiliated_invalidation_service_provider_impl.h
+++ b/chrome/browser/chromeos/policy/affiliated_invalidation_service_provider_impl.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_CHROMEOS_POLICY_AFFILIATED_INVALIDATION_SERVICE_PROVIDER_IMPL_H_
 
 #include <memory>
+#include <string>
 #include <vector>
 
 #include "base/macros.h"
@@ -17,7 +18,10 @@
 
 namespace invalidation {
 class InvalidationService;
-class TiclInvalidationService;
+}
+
+namespace instance_id {
+class InstanceIDDriver;
 }
 
 namespace policy {
@@ -26,7 +30,8 @@
     : public AffiliatedInvalidationServiceProvider,
       public content::NotificationObserver {
  public:
-  AffiliatedInvalidationServiceProviderImpl();
+  AffiliatedInvalidationServiceProviderImpl(bool is_fcm_enabled,
+                                            std::string fcm_sender_id);
   ~AffiliatedInvalidationServiceProviderImpl() override;
 
   // content::NotificationObserver:
@@ -39,8 +44,8 @@
   void UnregisterConsumer(Consumer* consumer) override;
   void Shutdown() override;
 
-  invalidation::TiclInvalidationService*
-      GetDeviceInvalidationServiceForTest() const;
+  invalidation::InvalidationService* GetDeviceInvalidationServiceForTest()
+      const;
 
  private:
   // Helper that monitors the status of a single |InvalidationService|.
@@ -69,19 +74,29 @@
   // Destroy the device-global invalidation service, if any.
   void DestroyDeviceInvalidationService();
 
-  content::NotificationRegistrar registrar_;
+  // Initializes and returns either TiclInvalidationService or
+  // FCMInvalidationService depending on the value of |is_fcm_enabled|.
+  std::unique_ptr<invalidation::InvalidationService>
+  InitializeDeviceInvalidationService();
 
-  // Device-global invalidation service.
-  std::unique_ptr<invalidation::TiclInvalidationService>
-      device_invalidation_service_;
+  content::NotificationRegistrar registrar_;
 
   // State observer for the device-global invalidation service.
   std::unique_ptr<InvalidationServiceObserver>
       device_invalidation_service_observer_;
 
-  // The |identity_provider_| must be declared before |invalidation_service_|
-  // becaise the service has a pointer to it.
-  std::unique_ptr<invalidation::IdentityProvider> identity_provider_;
+  // The |device_identity_provider_| must be declared before
+  // |device_invalidation_service_| because the service has a pointer to it.
+  std::unique_ptr<invalidation::IdentityProvider> device_identity_provider_;
+
+  // The |device_instance_id_driver_| must be declared before
+  // |device_invalidation_service_| because the service has a pointer to it. Not
+  // null only when FCM is enabled.
+  std::unique_ptr<instance_id::InstanceIDDriver> device_instance_id_driver_;
+
+  // Device-global invalidation service.
+  std::unique_ptr<invalidation::InvalidationService>
+      device_invalidation_service_;
 
   // State observers for logged-in users' invalidation services.
   std::vector<std::unique_ptr<InvalidationServiceObserver>>
@@ -97,6 +112,15 @@
 
   bool is_shut_down_;
 
+  // Whether FCM (Firebase Cloud Messaging) should be used for invalidating
+  // policies. If false, TICL (Tango Invalidation Client Library) is used
+  // instead.
+  const bool is_fcm_enabled_;
+
+  // Sender ID coming from the Firebase console. Is set only when
+  // |is_fcm_enabled_| is true.
+  const std::string fcm_sender_id_;
+
   DISALLOW_COPY_AND_ASSIGN(AffiliatedInvalidationServiceProviderImpl);
 };
 
diff --git a/chrome/browser/chromeos/policy/affiliated_invalidation_service_provider_impl_unittest.cc b/chrome/browser/chromeos/policy/affiliated_invalidation_service_provider_impl_unittest.cc
index 06f235a..0fabf88 100644
--- a/chrome/browser/chromeos/policy/affiliated_invalidation_service_provider_impl_unittest.cc
+++ b/chrome/browser/chromeos/policy/affiliated_invalidation_service_provider_impl_unittest.cc
@@ -12,11 +12,13 @@
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/ref_counted.h"
+#include "base/task/post_task.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/chromeos/settings/device_oauth2_token_service_factory.h"
 #include "chrome/browser/chromeos/settings/scoped_cros_settings_test_helper.h"
 #include "chrome/browser/invalidation/deprecated_profile_invalidation_provider_factory.h"
+#include "chrome/browser/invalidation/profile_invalidation_provider_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile_manager.h"
@@ -24,6 +26,7 @@
 #include "chromeos/dbus/cryptohome/cryptohome_client.h"
 #include "components/invalidation/impl/fake_invalidation_handler.h"
 #include "components/invalidation/impl/fake_invalidation_service.h"
+#include "components/invalidation/impl/fcm_invalidation_service.h"
 #include "components/invalidation/impl/profile_invalidation_provider.h"
 #include "components/invalidation/impl/ticl_invalidation_service.h"
 #include "components/invalidation/public/invalidation_service.h"
@@ -32,9 +35,14 @@
 #include "components/policy/core/common/cloud/cloud_policy_constants.h"
 #include "components/user_manager/scoped_user_manager.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/notification_details.h"
 #include "content/public/browser/notification_service.h"
+#include "content/public/common/service_manager_connection.h"
 #include "content/public/test/test_browser_thread_bundle.h"
+#include "services/data_decoder/public/cpp/safe_json_parser.h"
+#include "services/data_decoder/public/cpp/testing_json_parser.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
 #include "services/network/test/test_url_loader_factory.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -47,16 +55,54 @@
 const char kAffiliatedUserID2[] = "test_2@example.com";
 const char kUnaffiliatedUserID[] = "test@other_domain.test";
 
-std::unique_ptr<KeyedService> BuildProfileInvalidationProvider(
-    content::BrowserContext* context) {
+const char kFCMSenderId[] = "112233";
+
+std::unique_ptr<invalidation::InvalidationService>
+CreateInvalidationServiceForSenderId(const std::string& fcm_sender_id) {
   std::unique_ptr<invalidation::FakeInvalidationService> invalidation_service(
       new invalidation::FakeInvalidationService);
   invalidation_service->SetInvalidatorState(
       syncer::TRANSIENT_INVALIDATION_ERROR);
+  return invalidation_service;
+}
+
+std::unique_ptr<KeyedService> BuildProfileInvalidationProvider(
+    bool is_fcm_enabled,
+    content::BrowserContext* context) {
+  if (is_fcm_enabled) {
+    return std::make_unique<invalidation::ProfileInvalidationProvider>(
+        nullptr, nullptr,
+        base::BindRepeating(&CreateInvalidationServiceForSenderId));
+  }
+  auto invalidation_service =
+      std::make_unique<invalidation::FakeInvalidationService>();
+  invalidation_service->SetInvalidatorState(
+      syncer::TRANSIENT_INVALIDATION_ERROR);
   return std::make_unique<invalidation::ProfileInvalidationProvider>(
       std::move(invalidation_service), nullptr);
 }
 
+data_decoder::SafeJsonParser* CreateTestingJsonParser(
+    const std::string& unsafe_json,
+    data_decoder::SafeJsonParser::SuccessCallback success_callback,
+    data_decoder::SafeJsonParser::ErrorCallback error_callback) {
+  return new data_decoder::TestingJsonParser(
+      unsafe_json, std::move(success_callback), std::move(error_callback));
+}
+
+void SendInvalidatorStateChangeNotification(
+    invalidation::InvalidationService* service,
+    syncer::InvalidatorState state,
+    bool is_fcm_enabled) {
+  if (is_fcm_enabled) {
+    static_cast<invalidation::FCMInvalidationService*>(service)
+        ->OnInvalidatorStateChange(state);
+    return;
+  }
+  static_cast<invalidation::TiclInvalidationService*>(service)
+      ->OnInvalidatorStateChange(state);
+}
+
 }  // namespace
 
 // A simple AffiliatedInvalidationServiceProvider::Consumer that registers a
@@ -84,9 +130,12 @@
   DISALLOW_COPY_AND_ASSIGN(FakeConsumer);
 };
 
-class AffiliatedInvalidationServiceProviderImplTest : public testing::Test {
+// The param is is_fcm_enabled_. See below.
+class AffiliatedInvalidationServiceProviderImplTest
+    : public testing::TestWithParam<bool> {
  public:
   AffiliatedInvalidationServiceProviderImplTest();
+  ~AffiliatedInvalidationServiceProviderImplTest();
 
   // testing::Test:
   void SetUp() override;
@@ -128,8 +177,11 @@
                                  bool is_affiliated);
   std::unique_ptr<AffiliatedInvalidationServiceProviderImpl> provider_;
   std::unique_ptr<FakeConsumer> consumer_;
-  invalidation::TiclInvalidationService* device_invalidation_service_;
+  invalidation::InvalidationService* device_invalidation_service_;
   invalidation::FakeInvalidationService* profile_invalidation_service_;
+  // Boolean param, true if FCM (Firebase Cloud Messaging) is enabled,
+  // and false otherwise.
+  const bool is_fcm_enabled_;
 
  private:
   content::TestBrowserThreadBundle thread_bundle_;
@@ -191,11 +243,30 @@
     AffiliatedInvalidationServiceProviderImplTest()
     : device_invalidation_service_(nullptr),
       profile_invalidation_service_(nullptr),
+      is_fcm_enabled_(GetParam()),
       fake_user_manager_(new chromeos::FakeChromeUserManager),
       user_manager_enabler_(base::WrapUnique(fake_user_manager_)),
       profile_manager_(TestingBrowserProcess::GetGlobal()) {
   cros_settings_test_helper_.InstallAttributes()->SetCloudManaged("example.com",
                                                                   "device_id");
+
+  if (is_fcm_enabled_) {
+    data_decoder::SafeJsonParser::SetFactoryForTesting(
+        &CreateTestingJsonParser);
+
+    content::ServiceManagerConnection::SetForProcess(
+        content::ServiceManagerConnection::Create(
+            nullptr, base::CreateSingleThreadTaskRunnerWithTraits(
+                         {content::BrowserThread::IO})));
+  }
+}
+
+AffiliatedInvalidationServiceProviderImplTest::
+    ~AffiliatedInvalidationServiceProviderImplTest() {
+  if (is_fcm_enabled_) {
+    content::ServiceManagerConnection::DestroyForProcess();
+    data_decoder::SafeJsonParser::SetFactoryForTesting(nullptr);
+  }
 }
 
 void AffiliatedInvalidationServiceProviderImplTest::SetUp() {
@@ -207,11 +278,21 @@
       test_url_loader_factory_.GetSafeWeakWrapper(),
       TestingBrowserProcess::GetGlobal()->local_state());
 
-  invalidation::DeprecatedProfileInvalidationProviderFactory::GetInstance()
-      ->RegisterTestingFactory(
-          base::BindRepeating(&BuildProfileInvalidationProvider));
+  if (is_fcm_enabled_) {
+    invalidation::ProfileInvalidationProviderFactory::GetInstance()
+        ->RegisterTestingFactory(base::BindRepeating(
+            &BuildProfileInvalidationProvider, is_fcm_enabled_));
+  } else {
+    invalidation::DeprecatedProfileInvalidationProviderFactory::GetInstance()
+        ->RegisterTestingFactory(base::BindRepeating(
+            &BuildProfileInvalidationProvider, is_fcm_enabled_));
+  }
 
-  provider_ = std::make_unique<AffiliatedInvalidationServiceProviderImpl>();
+  provider_ = is_fcm_enabled_
+                  ? std::make_unique<AffiliatedInvalidationServiceProviderImpl>(
+                        is_fcm_enabled_, kFCMSenderId)
+                  : std::make_unique<AffiliatedInvalidationServiceProviderImpl>(
+                        is_fcm_enabled_, "");
 }
 
 void AffiliatedInvalidationServiceProviderImplTest::TearDown() {
@@ -219,9 +300,15 @@
   provider_->Shutdown();
   provider_.reset();
 
-  invalidation::DeprecatedProfileInvalidationProviderFactory::GetInstance()
-      ->RegisterTestingFactory(
-          BrowserContextKeyedServiceFactory::TestingFactory());
+  if (is_fcm_enabled_) {
+    invalidation::ProfileInvalidationProviderFactory::GetInstance()
+        ->RegisterTestingFactory(
+            BrowserContextKeyedServiceFactory::TestingFactory());
+  } else {
+    invalidation::DeprecatedProfileInvalidationProviderFactory::GetInstance()
+        ->RegisterTestingFactory(
+            BrowserContextKeyedServiceFactory::TestingFactory());
+  }
   chromeos::DeviceOAuth2TokenServiceFactory::Shutdown();
   chromeos::CryptohomeClient::Shutdown();
   chromeos::SystemSaltGetter::Shutdown();
@@ -311,8 +398,9 @@
   // Indicate that the device-global invalidation service has connected. Verify
   // that the consumer is informed about this.
   EXPECT_EQ(0, consumer_->GetAndClearInvalidationServiceSetCount());
-  device_invalidation_service_->OnInvalidatorStateChange(
-      syncer::INVALIDATIONS_ENABLED);
+  SendInvalidatorStateChangeNotification(device_invalidation_service_,
+                                         syncer::INVALIDATIONS_ENABLED,
+                                         is_fcm_enabled_);
   EXPECT_EQ(1, consumer_->GetAndClearInvalidationServiceSetCount());
   EXPECT_EQ(device_invalidation_service_, consumer_->GetInvalidationService());
 }
@@ -336,13 +424,26 @@
 invalidation::FakeInvalidationService*
 AffiliatedInvalidationServiceProviderImplTest::GetProfileInvalidationService(
     Profile* profile, bool create) {
-  invalidation::ProfileInvalidationProvider* invalidation_provider =
-      static_cast<invalidation::ProfileInvalidationProvider*>(
-          invalidation::DeprecatedProfileInvalidationProviderFactory::
-              GetInstance()
-                  ->GetServiceForBrowserContext(profile, create));
+  invalidation::ProfileInvalidationProvider* invalidation_provider;
+  if (is_fcm_enabled_) {
+    invalidation_provider =
+        static_cast<invalidation::ProfileInvalidationProvider*>(
+            invalidation::ProfileInvalidationProviderFactory::GetInstance()
+                ->GetServiceForBrowserContext(profile, create));
+  } else {
+    invalidation_provider =
+        static_cast<invalidation::ProfileInvalidationProvider*>(
+            invalidation::DeprecatedProfileInvalidationProviderFactory::
+                GetInstance()
+                    ->GetServiceForBrowserContext(profile, create));
+  }
   if (!invalidation_provider)
     return nullptr;
+  if (is_fcm_enabled_) {
+    return static_cast<invalidation::FakeInvalidationService*>(
+        invalidation_provider->GetInvalidationServiceForCustomSender(
+            kFCMSenderId));
+  }
   return static_cast<invalidation::FakeInvalidationService*>(
       invalidation_provider->GetInvalidationService());
 }
@@ -351,7 +452,7 @@
 // AffiliatedInvalidationServiceProviderImpl. Verifies that no device-global
 // invalidation service is created, whether an affiliated user is logged in or
 // not.
-TEST_F(AffiliatedInvalidationServiceProviderImplTest, NoConsumers) {
+TEST_P(AffiliatedInvalidationServiceProviderImplTest, NoConsumers) {
   // Verify that no device-global invalidation service has been created.
   EXPECT_FALSE(provider_->GetDeviceInvalidationServiceForTest());
 
@@ -364,7 +465,7 @@
 
 // Verifies that when no connected invalidation service is available for use,
 // none is made available to consumers.
-TEST_F(AffiliatedInvalidationServiceProviderImplTest,
+TEST_P(AffiliatedInvalidationServiceProviderImplTest,
        NoInvalidationServiceAvailable) {
   // Register a consumer. Verify that the consumer is not called back
   // immediately as no connected invalidation service exists yet.
@@ -377,7 +478,7 @@
 // affiliated user is available, a device-global invalidation service is
 // created. Further verifies that when the device-global invalidation service
 // connects, it is made available to the consumer.
-TEST_F(AffiliatedInvalidationServiceProviderImplTest,
+TEST_P(AffiliatedInvalidationServiceProviderImplTest,
        UseDeviceInvalidationService) {
   consumer_.reset(new FakeConsumer(provider_.get()));
 
@@ -388,8 +489,9 @@
   // Indicate that the device-global invalidation service has disconnected.
   // Verify that the consumer is informed about this.
   EXPECT_EQ(0, consumer_->GetAndClearInvalidationServiceSetCount());
-  device_invalidation_service_->OnInvalidatorStateChange(
-      syncer::INVALIDATION_CREDENTIALS_REJECTED);
+  SendInvalidatorStateChangeNotification(
+      device_invalidation_service_, syncer::INVALIDATION_CREDENTIALS_REJECTED,
+      is_fcm_enabled_);
   EXPECT_EQ(1, consumer_->GetAndClearInvalidationServiceSetCount());
   EXPECT_EQ(nullptr, consumer_->GetInvalidationService());
 
@@ -400,7 +502,7 @@
 // A consumer is registered with the AffiliatedInvalidationServiceProviderImpl.
 // Verifies that when a per-profile invalidation service belonging to an
 // affiliated user connects, it is made available to the consumer.
-TEST_F(AffiliatedInvalidationServiceProviderImplTest,
+TEST_P(AffiliatedInvalidationServiceProviderImplTest,
        UseAffiliatedProfileInvalidationService) {
   consumer_.reset(new FakeConsumer(provider_.get()));
 
@@ -422,7 +524,7 @@
 // A consumer is registered with the AffiliatedInvalidationServiceProviderImpl.
 // Verifies that when a per-profile invalidation service belonging to an
 // unaffiliated user connects, it is ignored.
-TEST_F(AffiliatedInvalidationServiceProviderImplTest,
+TEST_P(AffiliatedInvalidationServiceProviderImplTest,
        DoNotUseUnaffiliatedProfileInvalidationService) {
   consumer_.reset(new FakeConsumer(provider_.get()));
 
@@ -441,7 +543,7 @@
 // available to the consumer. Verifies that when a per-profile invalidation
 // service belonging to an affiliated user connects, it is made available to the
 // consumer instead and the device-global invalidation service is destroyed.
-TEST_F(AffiliatedInvalidationServiceProviderImplTest,
+TEST_P(AffiliatedInvalidationServiceProviderImplTest,
        SwitchToAffiliatedProfileInvalidationService) {
   consumer_.reset(new FakeConsumer(provider_.get()));
 
@@ -462,7 +564,7 @@
 // service belonging to an unaffiliated user connects, it is ignored and the
 // device-global invalidation service continues to be made available to the
 // consumer.
-TEST_F(AffiliatedInvalidationServiceProviderImplTest,
+TEST_P(AffiliatedInvalidationServiceProviderImplTest,
        DoNotSwitchToUnaffiliatedProfileInvalidationService) {
   consumer_.reset(new FakeConsumer(provider_.get()));
 
@@ -483,7 +585,7 @@
 // per-profile invalidation service disconnects, a device-global invalidation
 // service is created. Further verifies that when the device-global invalidation
 // service connects, it is made available to the consumer.
-TEST_F(AffiliatedInvalidationServiceProviderImplTest,
+TEST_P(AffiliatedInvalidationServiceProviderImplTest,
        SwitchToDeviceInvalidationService) {
   consumer_.reset(new FakeConsumer(provider_.get()));
 
@@ -513,7 +615,7 @@
 // connected. Verifies that when the per-profile invalidation service belonging
 // to the first user disconnects, the per-profile invalidation service belonging
 // to the second user is made available to the consumer instead.
-TEST_F(AffiliatedInvalidationServiceProviderImplTest,
+TEST_P(AffiliatedInvalidationServiceProviderImplTest,
        SwitchBetweenAffiliatedProfileInvalidationServices) {
   consumer_.reset(new FakeConsumer(provider_.get()));
 
@@ -568,7 +670,7 @@
 // invalidation service is not destroyed and remains available to the second
 // consumer. Further verifies that when the second consumer also unregisters,
 // the device-global invalidation service is destroyed.
-TEST_F(AffiliatedInvalidationServiceProviderImplTest, MultipleConsumers) {
+TEST_P(AffiliatedInvalidationServiceProviderImplTest, MultipleConsumers) {
   consumer_.reset(new FakeConsumer(provider_.get()));
 
   // Indicate that the device-global invalidation service connected. Verify that
@@ -604,7 +706,7 @@
 // device-global invalidation service is created and a per-profile invalidation
 // service belonging to a second affiliated user that subsequently connects is
 // ignored.
-TEST_F(AffiliatedInvalidationServiceProviderImplTest, NoServiceAfterShutdown) {
+TEST_P(AffiliatedInvalidationServiceProviderImplTest, NoServiceAfterShutdown) {
   consumer_.reset(new FakeConsumer(provider_.get()));
 
   // Verify that a device-global invalidation service has been created.
@@ -654,7 +756,7 @@
 // consumer is informed that no invalidation service is available for use
 // anymore before the device-global invalidation service is destroyed.
 // This is a regression test for http://crbug.com/455504.
-TEST_F(AffiliatedInvalidationServiceProviderImplTest,
+TEST_P(AffiliatedInvalidationServiceProviderImplTest,
        ConnectedDeviceGlobalInvalidationServiceOnShutdown) {
   consumer_.reset(new FakeConsumer(provider_.get()));
 
@@ -680,4 +782,8 @@
   EXPECT_FALSE(provider_->GetDeviceInvalidationServiceForTest());
 }
 
+INSTANTIATE_TEST_SUITE_P(FCMEnabledAndFCMDisabled,
+                         AffiliatedInvalidationServiceProviderImplTest,
+                         testing::Bool() /* is_fcm_enabled */);
+
 }  // namespace policy
diff --git a/chrome/browser/chromeos/policy/browser_policy_connector_chromeos.cc b/chrome/browser/chromeos/policy/browser_policy_connector_chromeos.cc
index 4d5260c..6f8ebe1 100644
--- a/chrome/browser/chromeos/policy/browser_policy_connector_chromeos.cc
+++ b/chrome/browser/chromeos/policy/browser_policy_connector_chromeos.cc
@@ -45,6 +45,7 @@
 #include "chrome/browser/chromeos/settings/device_settings_service.h"
 #include "chrome/browser/chromeos/system/timezone_util.h"
 #include "chrome/browser/policy/device_management_service_configuration.h"
+#include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
 #include "chromeos/attestation/attestation_flow.h"
 #include "chromeos/constants/chromeos_paths.h"
@@ -64,6 +65,7 @@
 #include "chromeos/system/statistics_provider.h"
 #include "chromeos/tpm/install_attributes.h"
 #include "components/policy/core/common/cloud/cloud_policy_client.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
 #include "components/policy/core/common/cloud/cloud_policy_refresh_scheduler.h"
 #include "components/policy/core/common/cloud/resource_cache.h"
 #include "components/policy/core/common/proxy_policy_provider.h"
@@ -165,8 +167,12 @@
   local_state_ = local_state;
   ChromeBrowserPolicyConnector::Init(local_state, url_loader_factory);
 
+  const bool is_fcm_enabled =
+      base::FeatureList::IsEnabled(features::kPolicyFcmInvalidations);
   affiliated_invalidation_service_provider_ =
-      std::make_unique<AffiliatedInvalidationServiceProviderImpl>();
+      std::make_unique<AffiliatedInvalidationServiceProviderImpl>(
+          is_fcm_enabled,
+          is_fcm_enabled ? policy::kPolicyFCMInvalidationSenderID : "");
 
   if (device_cloud_policy_manager_) {
     // Note: for now the |device_cloud_policy_manager_| is using the global
@@ -196,7 +202,7 @@
         std::make_unique<AffiliatedCloudPolicyInvalidator>(
             em::DeviceRegisterRequest::DEVICE,
             device_cloud_policy_manager_->core(),
-            affiliated_invalidation_service_provider_.get());
+            affiliated_invalidation_service_provider_.get(), is_fcm_enabled);
     device_remote_commands_invalidator_ =
         std::make_unique<AffiliatedRemoteCommandsInvalidator>(
             device_cloud_policy_manager_->core(),
diff --git a/chrome/browser/chromeos/policy/configuration_policy_handler_chromeos.cc b/chrome/browser/chromeos/policy/configuration_policy_handler_chromeos.cc
index 561cb41..f9ea7db 100644
--- a/chrome/browser/chromeos/policy/configuration_policy_handler_chromeos.cc
+++ b/chrome/browser/chromeos/policy/configuration_policy_handler_chromeos.cc
@@ -340,7 +340,7 @@
 ScreenMagnifierPolicyHandler::ScreenMagnifierPolicyHandler()
     : IntRangePolicyHandlerBase(key::kScreenMagnifierType,
                                 chromeos::MAGNIFIER_DISABLED,
-                                chromeos::MAGNIFIER_FULL,
+                                chromeos::MAGNIFIER_DOCKED,
                                 false) {}
 
 ScreenMagnifierPolicyHandler::~ScreenMagnifierPolicyHandler() {
@@ -352,10 +352,10 @@
   const base::Value* value = policies.GetValue(policy_name());
   int value_in_range;
   if (value && EnsureInRange(value, &value_in_range, nullptr)) {
-    // The "type" is only used to enable or disable the feature as a whole.
-    // http://crbug.com/170850
     prefs->SetBoolean(ash::prefs::kAccessibilityScreenMagnifierEnabled,
-                      value_in_range != chromeos::MAGNIFIER_DISABLED);
+                      value_in_range == chromeos::MAGNIFIER_FULL);
+    prefs->SetBoolean(ash::prefs::kDockedMagnifierEnabled,
+                      value_in_range == chromeos::MAGNIFIER_DOCKED);
   }
 }
 
diff --git a/chrome/browser/chromeos/policy/device_local_account_policy_service.cc b/chrome/browser/chromeos/policy/device_local_account_policy_service.cc
index 6f3e1b1..e03310d 100644
--- a/chrome/browser/chromeos/policy/device_local_account_policy_service.cc
+++ b/chrome/browser/chromeos/policy/device_local_account_policy_service.cc
@@ -27,6 +27,7 @@
 #include "chrome/browser/chromeos/policy/device_local_account_policy_store.h"
 #include "chrome/browser/chromeos/settings/device_settings_service.h"
 #include "chrome/common/chrome_content_client.h"
+#include "chrome/common/chrome_features.h"
 #include "chromeos/constants/chromeos_paths.h"
 #include "chromeos/dbus/session_manager/session_manager_client.h"
 #include "chromeos/settings/cros_settings_names.h"
@@ -209,9 +210,8 @@
   core_.StartRefreshScheduler();
   UpdateRefreshDelay();
   invalidator_.reset(new AffiliatedCloudPolicyInvalidator(
-      em::DeviceRegisterRequest::DEVICE,
-      &core_,
-      invalidation_service_provider_));
+      em::DeviceRegisterRequest::DEVICE, &core_, invalidation_service_provider_,
+      base::FeatureList::IsEnabled(features::kPolicyFcmInvalidations)));
 }
 
 void DeviceLocalAccountPolicyBroker::UpdateRefreshDelay() {
diff --git a/chrome/browser/net/system_network_context_manager.cc b/chrome/browser/net/system_network_context_manager.cc
index ec1072f..1450d902a 100644
--- a/chrome/browser/net/system_network_context_manager.cc
+++ b/chrome/browser/net/system_network_context_manager.cc
@@ -61,6 +61,7 @@
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/mojom/host_resolver.mojom.h"
 #include "services/proxy_resolver/public/mojom/proxy_resolver.mojom.h"
+#include "third_party/blink/public/common/features.h"
 #include "url/gurl.h"
 
 #if defined(OS_ANDROID)
@@ -642,19 +643,25 @@
   // respect prefs::kEnableReferrers from the appropriate pref store.
   network_context_params->enable_referrers = false;
 
-  std::string quic_user_agent_id = chrome::GetChannelName();
-  if (!quic_user_agent_id.empty())
-    quic_user_agent_id.push_back(' ');
-  quic_user_agent_id.append(
-      version_info::GetProductNameAndVersionForUserAgent());
-  quic_user_agent_id.push_back(' ');
-  quic_user_agent_id.append(
-      content::BuildOSCpuInfo(false /* include_android_build_number */));
-  network_context_params->quic_user_agent_id = quic_user_agent_id;
-
   const base::CommandLine& command_line =
       *base::CommandLine::ForCurrentProcess();
 
+  std::string quic_user_agent_id;
+
+  if (base::FeatureList::IsEnabled(blink::features::kFreezeUserAgent)) {
+    quic_user_agent_id = "";
+  } else {
+    quic_user_agent_id = chrome::GetChannelName();
+    if (!quic_user_agent_id.empty())
+      quic_user_agent_id.push_back(' ');
+    quic_user_agent_id.append(
+        version_info::GetProductNameAndVersionForUserAgent());
+    quic_user_agent_id.push_back(' ');
+    quic_user_agent_id.append(
+        content::BuildOSCpuInfo(false /* include_android_build_number */));
+  }
+  network_context_params->quic_user_agent_id = quic_user_agent_id;
+
   // TODO(eroman): Figure out why this doesn't work in single-process mode,
   // or if it does work, now.
   // Should be possible now that a private isolate is used.
diff --git a/chrome/browser/net/system_network_context_manager_browsertest.cc b/chrome/browser/net/system_network_context_manager_browsertest.cc
index 5d917e5..f340365f 100644
--- a/chrome/browser/net/system_network_context_manager_browsertest.cc
+++ b/chrome/browser/net/system_network_context_manager_browsertest.cc
@@ -13,14 +13,19 @@
 #include "base/values.h"
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/common/channel_info.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "components/prefs/pref_service.h"
+#include "components/version_info/version_info.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/user_agent.h"
 #include "services/network/public/mojom/network_context.mojom.h"
 #include "services/network/public/mojom/network_service.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/features.h"
 
 namespace {
 
@@ -297,3 +302,45 @@
 INSTANTIATE_TEST_SUITE_P(,
                          SystemNetworkContextManagerStubResolverBrowsertest,
                          ::testing::Values(false, true));
+
+class SystemNetworkContextManagerFreezeQUICUaBrowsertest
+    : public SystemNetworkContextManagerBrowsertest,
+      public testing::WithParamInterface<bool> {
+ public:
+  SystemNetworkContextManagerFreezeQUICUaBrowsertest() {
+    scoped_feature_list_.InitWithFeatureState(blink::features::kFreezeUserAgent,
+                                              GetParam());
+  }
+  ~SystemNetworkContextManagerFreezeQUICUaBrowsertest() override {}
+
+  void SetUpOnMainThread() override {}
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+bool ContainsSubstring(std::string super, std::string sub) {
+  return super.find(sub) != std::string::npos;
+}
+
+IN_PROC_BROWSER_TEST_P(SystemNetworkContextManagerFreezeQUICUaBrowsertest,
+                       QUICUaConfig) {
+  network::mojom::NetworkContextParamsPtr network_context_params =
+      g_browser_process->system_network_context_manager()
+          ->CreateDefaultNetworkContextParams();
+
+  std::string quic_ua = network_context_params->quic_user_agent_id;
+
+  if (GetParam()) {  // if the UA Freeze feature is turned on
+    EXPECT_EQ("", quic_ua);
+  } else {
+    EXPECT_TRUE(ContainsSubstring(quic_ua, chrome::GetChannelName()));
+    EXPECT_TRUE(ContainsSubstring(
+        quic_ua, version_info::GetProductNameAndVersionForUserAgent()));
+    EXPECT_TRUE(ContainsSubstring(quic_ua, content::BuildOSCpuInfo(false)));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(,
+                         SystemNetworkContextManagerFreezeQUICUaBrowsertest,
+                         ::testing::Values(true, false));
diff --git a/chrome/browser/password_manager/password_accessory_metrics_util.h b/chrome/browser/password_manager/password_accessory_metrics_util.h
index 240d9a4..ca36c8b 100644
--- a/chrome/browser/password_manager/password_accessory_metrics_util.h
+++ b/chrome/browser/password_manager/password_accessory_metrics_util.h
@@ -43,6 +43,7 @@
   PASSWORD = 1,
   PAYMENT_INFO = 2,
   ADDRESS_INFO = 3,
+  TOUCH_TO_FILL_INFO = 4,
   COUNT,
 };
 
diff --git a/chrome/browser/password_manager/password_store_x.cc b/chrome/browser/password_manager/password_store_x.cc
index 394977ce..e6994c2 100644
--- a/chrome/browser/password_manager/password_store_x.cc
+++ b/chrome/browser/password_manager/password_store_x.cc
@@ -188,13 +188,14 @@
 }
 
 PasswordStoreChangeList PasswordStoreX::UpdateLoginImpl(
-    const PasswordForm& form) {
+    const PasswordForm& form,
+    password_manager::UpdateLoginError* error) {
   CheckMigration();
   PasswordStoreChangeList changes;
   if (use_native_backend() && backend_->UpdateLogin(form, &changes)) {
     allow_fallback_ = false;
   } else if (allow_default_store()) {
-    changes = PasswordStoreDefault::UpdateLoginImpl(form);
+    changes = PasswordStoreDefault::UpdateLoginImpl(form, error);
   }
   return changes;
 }
diff --git a/chrome/browser/password_manager/password_store_x.h b/chrome/browser/password_manager/password_store_x.h
index 9b4bf0c..8eb0b6f 100644
--- a/chrome/browser/password_manager/password_store_x.h
+++ b/chrome/browser/password_manager/password_store_x.h
@@ -158,7 +158,8 @@
       const autofill::PasswordForm& form,
       password_manager::AddLoginError* error = nullptr) override;
   password_manager::PasswordStoreChangeList UpdateLoginImpl(
-      const autofill::PasswordForm& form) override;
+      const autofill::PasswordForm& form,
+      password_manager::UpdateLoginError* error = nullptr) override;
   password_manager::PasswordStoreChangeList RemoveLoginImpl(
       const autofill::PasswordForm& form) override;
   password_manager::PasswordStoreChangeList RemoveLoginsByURLAndTimeImpl(
diff --git a/chrome/browser/policy/cloud/cloud_policy_invalidator.cc b/chrome/browser/policy/cloud/cloud_policy_invalidator.cc
index 5631ae9..ec6035e 100644
--- a/chrome/browser/policy/cloud/cloud_policy_invalidator.cc
+++ b/chrome/browser/policy/cloud/cloud_policy_invalidator.cc
@@ -25,6 +25,11 @@
 
 namespace policy {
 
+namespace {
+
+constexpr char kFcmPolicyPublicTopicPrefix[] = "cs-";
+}  // namespace
+
 const int CloudPolicyInvalidator::kMissingPayloadDelay = 5;
 const int CloudPolicyInvalidator::kMaxFetchDelayDefault = 10000;
 const int CloudPolicyInvalidator::kMaxFetchDelayMin = 1000;
@@ -38,7 +43,8 @@
     CloudPolicyCore* core,
     const scoped_refptr<base::SequencedTaskRunner>& task_runner,
     base::Clock* clock,
-    int64_t highest_handled_invalidation_version)
+    int64_t highest_handled_invalidation_version,
+    bool is_fcm_enabled)
     : state_(UNINITIALIZED),
       type_(type),
       core_(core),
@@ -55,6 +61,7 @@
           highest_handled_invalidation_version),
       max_fetch_delay_(kMaxFetchDelayDefault),
       policy_hash_value_(0),
+      is_fcm_enabled_(is_fcm_enabled),
       weak_factory_(this) {
   DCHECK(core);
   DCHECK(task_runner.get());
@@ -132,6 +139,10 @@
 
 std::string CloudPolicyInvalidator::GetOwnerName() const { return "Cloud"; }
 
+bool CloudPolicyInvalidator::IsPublicTopic(const syncer::Topic& topic) const {
+  return base::StringPiece(topic).starts_with(kFcmPolicyPublicTopicPrefix);
+}
+
 void CloudPolicyInvalidator::OnCoreConnected(CloudPolicyCore* core) {}
 
 void CloudPolicyInvalidator::OnRefreshSchedulerStarted(CloudPolicyCore* core) {
@@ -275,16 +286,31 @@
 void CloudPolicyInvalidator::UpdateRegistration(
     const enterprise_management::PolicyData* policy) {
   // Create the ObjectId based on the policy data.
-  // If the policy does not specify an the ObjectId, then unregister.
-  if (!policy ||
-      !policy->has_invalidation_source() ||
-      !policy->has_invalidation_name()) {
+  if (!policy) {
     Unregister();
     return;
   }
-  invalidation::ObjectId object_id(
-      policy->invalidation_source(),
-      policy->invalidation_name());
+
+  // If the policy does not specify an ObjectId, then unregister.
+  invalidation::ObjectId object_id;
+  if (is_fcm_enabled_) {
+    if (!policy->has_policy_invalidation_topic() ||
+        policy->policy_invalidation_topic().empty()) {
+      Unregister();
+      return;
+    }
+    object_id = invalidation::ObjectId(syncer::kDeprecatedSourceForFCM,
+                                       policy->policy_invalidation_topic());
+  } else {
+    if (!policy->has_invalidation_source() ||
+        !policy->has_invalidation_name() ||
+        policy->invalidation_name().empty()) {
+      Unregister();
+      return;
+    }
+    object_id = invalidation::ObjectId(policy->invalidation_source(),
+                                       policy->invalidation_name());
+  }
 
   // If the policy object id in the policy data is different from the currently
   // registered object id, update the object registration.
diff --git a/chrome/browser/policy/cloud/cloud_policy_invalidator.h b/chrome/browser/policy/cloud/cloud_policy_invalidator.h
index 9da9400..ccbf9614 100644
--- a/chrome/browser/policy/cloud/cloud_policy_invalidator.h
+++ b/chrome/browser/policy/cloud/cloud_policy_invalidator.h
@@ -75,7 +75,8 @@
       CloudPolicyCore* core,
       const scoped_refptr<base::SequencedTaskRunner>& task_runner,
       base::Clock* clock,
-      int64_t highest_handled_invalidation_version);
+      int64_t highest_handled_invalidation_version,
+      bool is_fcm_enabled);
   ~CloudPolicyInvalidator() override;
 
   // Initializes the invalidator. No invalidations will be generated before this
@@ -93,6 +94,10 @@
     return invalidations_enabled_;
   }
 
+  // Whether to use FCM (Firebase Cloud Messaging) topics when registering
+  // to invalidations.
+  bool is_fcm_enabled() { return is_fcm_enabled_; }
+
   // The highest invalidation version that was handled already.
   int64_t highest_handled_invalidation_version() const {
     return highest_handled_invalidation_version_;
@@ -107,6 +112,7 @@
   void OnIncomingInvalidation(
       const syncer::ObjectIdInvalidationMap& invalidation_map) override;
   std::string GetOwnerName() const override;
+  bool IsPublicTopic(const syncer::Topic& topic) const override;
 
   // CloudPolicyCore::Observer:
   void OnCoreConnected(CloudPolicyCore* core) override;
@@ -237,6 +243,10 @@
   // policy is different from the current one.
   uint32_t policy_hash_value_;
 
+  // Whether the CloudPolicyInvalidator should use FCM (Firebase Cloud
+  // Messaging) topics.
+  const bool is_fcm_enabled_;
+
   // A thread checker to make sure that callbacks are invoked on the correct
   // thread.
   base::ThreadChecker thread_checker_;
diff --git a/chrome/browser/policy/cloud/cloud_policy_invalidator_unittest.cc b/chrome/browser/policy/cloud/cloud_policy_invalidator_unittest.cc
index f056d4b..8f76ce2 100644
--- a/chrome/browser/policy/cloud/cloud_policy_invalidator_unittest.cc
+++ b/chrome/browser/policy/cloud/cloud_policy_invalidator_unittest.cc
@@ -42,7 +42,19 @@
 
 namespace policy {
 
-class CloudPolicyInvalidatorTest : public testing::Test {
+namespace {
+
+struct TestParams {
+  const bool is_fcm_enabled;
+  const em::DeviceRegisterRequest::Type policy_type;
+
+  TestParams(bool is_fcm_enabled, em::DeviceRegisterRequest::Type policy_type)
+      : is_fcm_enabled(is_fcm_enabled), policy_type(std::move(policy_type)) {}
+};
+
+}  // namespace
+
+class CloudPolicyInvalidatorTestBase : public testing::Test {
  protected:
   // Policy objects which can be used in tests.
   enum PolicyObject {
@@ -51,7 +63,7 @@
     POLICY_OBJECT_B
   };
 
-  CloudPolicyInvalidatorTest();
+  explicit CloudPolicyInvalidatorTestBase(bool is_fcm_enabled);
 
   void TearDown() override;
 
@@ -175,6 +187,10 @@
   // Get the policy type that the |invalidator_| is responsible for.
   virtual em::DeviceRegisterRequest::Type GetPolicyType() const;
 
+  // if true FCMInvalidationService is used as InvalidationService and
+  // TiclInvalidationService otherwise.
+  const bool is_fcm_enabled_;
+
  private:
   // Checks that the policy was refreshed due to an invalidation with the given
   // base delay.
@@ -212,36 +228,45 @@
   const char* policy_value_cur_;
 };
 
-CloudPolicyInvalidatorTest::CloudPolicyInvalidatorTest()
-    : core_(dm_protocol::kChromeUserPolicyType,
+CloudPolicyInvalidatorTestBase::CloudPolicyInvalidatorTestBase(
+    bool is_fcm_enabled)
+    : is_fcm_enabled_(is_fcm_enabled),
+      core_(dm_protocol::kChromeUserPolicyType,
             std::string(),
             &store_,
             loop_.task_runner(),
             network::TestNetworkConnectionTracker::CreateGetter()),
       client_(nullptr),
       task_runner_(new base::TestSimpleTaskRunner()),
-      object_id_a_(135, "asdf"),
-      object_id_b_(246, "zxcv"),
       policy_value_a_("asdf"),
       policy_value_b_("zxcv"),
       policy_value_cur_(policy_value_a_) {
+  if (is_fcm_enabled_) {
+    object_id_a_ =
+        invalidation::ObjectId(syncer::kDeprecatedSourceForFCM, "asdf");
+    object_id_b_ =
+        invalidation::ObjectId(syncer::kDeprecatedSourceForFCM, "zxcv");
+  } else {
+    object_id_a_ = invalidation::ObjectId(135, "asdf");
+    object_id_b_ = invalidation::ObjectId(246, "zxcv");
+  }
   clock_.SetNow(base::Time::UnixEpoch() +
                 base::TimeDelta::FromSeconds(987654321));
 }
 
-void CloudPolicyInvalidatorTest::TearDown() {
+void CloudPolicyInvalidatorTestBase::TearDown() {
   if (invalidator_)
     invalidator_->Shutdown();
   core_.Disconnect();
 }
 
-void CloudPolicyInvalidatorTest::StartInvalidator(
+void CloudPolicyInvalidatorTestBase::StartInvalidator(
     bool initialize,
     bool start_refresh_scheduler,
     int64_t highest_handled_invalidation_version) {
-  invalidator_.reset(
-      new CloudPolicyInvalidator(GetPolicyType(), &core_, task_runner_, &clock_,
-                                 highest_handled_invalidation_version));
+  invalidator_.reset(new CloudPolicyInvalidator(
+      GetPolicyType(), &core_, task_runner_, &clock_,
+      highest_handled_invalidation_version, is_fcm_enabled_));
   if (start_refresh_scheduler) {
     ConnectCore();
     StartRefreshScheduler();
@@ -250,41 +275,45 @@
     InitializeInvalidator();
 }
 
-void CloudPolicyInvalidatorTest::InitializeInvalidator() {
+void CloudPolicyInvalidatorTestBase::InitializeInvalidator() {
   invalidator_->Initialize(&invalidation_service_);
 }
 
-void CloudPolicyInvalidatorTest::ShutdownInvalidator() {
+void CloudPolicyInvalidatorTestBase::ShutdownInvalidator() {
   invalidator_->Shutdown();
 }
 
-void CloudPolicyInvalidatorTest::DestroyInvalidator() {
+void CloudPolicyInvalidatorTestBase::DestroyInvalidator() {
   invalidator_.reset();
 }
 
-void CloudPolicyInvalidatorTest::ConnectCore() {
+void CloudPolicyInvalidatorTestBase::ConnectCore() {
   client_ = new MockCloudPolicyClient();
   client_->SetDMToken("dm");
   core_.Connect(std::unique_ptr<CloudPolicyClient>(client_));
 }
 
-void CloudPolicyInvalidatorTest::StartRefreshScheduler() {
+void CloudPolicyInvalidatorTestBase::StartRefreshScheduler() {
   core_.StartRefreshScheduler();
 }
 
-void CloudPolicyInvalidatorTest::DisconnectCore() {
+void CloudPolicyInvalidatorTestBase::DisconnectCore() {
   client_ = nullptr;
   core_.Disconnect();
 }
 
-void CloudPolicyInvalidatorTest::StorePolicy(PolicyObject object,
-                                             int64_t invalidation_version,
-                                             bool policy_changed,
-                                             const base::Time& time) {
+void CloudPolicyInvalidatorTestBase::StorePolicy(PolicyObject object,
+                                                 int64_t invalidation_version,
+                                                 bool policy_changed,
+                                                 const base::Time& time) {
   em::PolicyData* data = new em::PolicyData();
   if (object != POLICY_OBJECT_NONE) {
     data->set_invalidation_source(GetPolicyObjectId(object).source());
     data->set_invalidation_name(GetPolicyObjectId(object).name());
+    // When FCM is enabled CloudPolicyInvalidator expects the name in this
+    // field.
+    if (is_fcm_enabled_)
+      data->set_policy_invalidation_topic(GetPolicyObjectId(object).name());
   }
   data->set_timestamp(time.ToJavaTime());
   // Swap the policy value if a policy change is desired.
@@ -306,16 +335,16 @@
   store_.NotifyStoreLoaded();
 }
 
-void CloudPolicyInvalidatorTest::DisableInvalidationService() {
+void CloudPolicyInvalidatorTestBase::DisableInvalidationService() {
   invalidation_service_.SetInvalidatorState(
       syncer::TRANSIENT_INVALIDATION_ERROR);
 }
 
-void CloudPolicyInvalidatorTest::EnableInvalidationService() {
+void CloudPolicyInvalidatorTestBase::EnableInvalidationService() {
   invalidation_service_.SetInvalidatorState(syncer::INVALIDATIONS_ENABLED);
 }
 
-syncer::Invalidation CloudPolicyInvalidatorTest::FireInvalidation(
+syncer::Invalidation CloudPolicyInvalidatorTestBase::FireInvalidation(
     PolicyObject object,
     int64_t version,
     const std::string& payload) {
@@ -327,7 +356,8 @@
   return invalidation;
 }
 
-syncer::Invalidation CloudPolicyInvalidatorTest::FireUnknownVersionInvalidation(
+syncer::Invalidation
+CloudPolicyInvalidatorTestBase::FireUnknownVersionInvalidation(
     PolicyObject object) {
   syncer::Invalidation invalidation = syncer::Invalidation::InitUnknownVersion(
       GetPolicyObjectId(object));
@@ -335,7 +365,7 @@
   return invalidation;
 }
 
-bool CloudPolicyInvalidatorTest::CheckInvalidationInfo(
+bool CloudPolicyInvalidatorTestBase::CheckInvalidationInfo(
     int64_t version,
     const std::string& payload) {
   MockCloudPolicyClient* client =
@@ -344,29 +374,29 @@
       payload == client->invalidation_payload_;
 }
 
-bool CloudPolicyInvalidatorTest::CheckPolicyNotRefreshed() {
+bool CloudPolicyInvalidatorTestBase::CheckPolicyNotRefreshed() {
   return CheckPolicyRefreshCount(0);
 }
 
-bool CloudPolicyInvalidatorTest::CheckPolicyRefreshed() {
+bool CloudPolicyInvalidatorTestBase::CheckPolicyRefreshed() {
   return CheckPolicyRefreshed(base::TimeDelta());
 }
 
-bool CloudPolicyInvalidatorTest::IsUnsent(
+bool CloudPolicyInvalidatorTestBase::IsUnsent(
     const syncer::Invalidation& invalidation) {
   return invalidation_service_.GetMockAckHandler()->IsUnsent(invalidation);
 }
 
-bool CloudPolicyInvalidatorTest::CheckPolicyRefreshedWithUnknownVersion() {
+bool CloudPolicyInvalidatorTestBase::CheckPolicyRefreshedWithUnknownVersion() {
   return CheckPolicyRefreshed(base::TimeDelta::FromMinutes(
         CloudPolicyInvalidator::kMissingPayloadDelay));
 }
 
-bool CloudPolicyInvalidatorTest::InvalidationsEnabled() {
+bool CloudPolicyInvalidatorTestBase::InvalidationsEnabled() {
   return core_.refresh_scheduler()->invalidations_available();
 }
 
-bool CloudPolicyInvalidatorTest::IsInvalidationAcknowledged(
+bool CloudPolicyInvalidatorTestBase::IsInvalidationAcknowledged(
     const syncer::Invalidation& invalidation) {
   // The acknowledgement task is run through a WeakHandle that posts back to our
   // own thread.  We need to run any posted tasks before we can check
@@ -377,38 +407,39 @@
   return !invalidation_service_.GetMockAckHandler()->IsUnacked(invalidation);
 }
 
-bool CloudPolicyInvalidatorTest::IsInvalidatorRegistered() {
+bool CloudPolicyInvalidatorTestBase::IsInvalidatorRegistered() {
   return !invalidation_service_.invalidator_registrar()
       .GetRegisteredIds(invalidator_.get()).empty();
 }
 
-int64_t CloudPolicyInvalidatorTest::GetHighestHandledInvalidationVersion()
+int64_t CloudPolicyInvalidatorTestBase::GetHighestHandledInvalidationVersion()
     const {
   return invalidator_->highest_handled_invalidation_version();
 }
 
-void CloudPolicyInvalidatorTest::AdvanceClock(base::TimeDelta delta) {
+void CloudPolicyInvalidatorTestBase::AdvanceClock(base::TimeDelta delta) {
   clock_.Advance(delta);
 }
 
-base::Time CloudPolicyInvalidatorTest::Now() {
+base::Time CloudPolicyInvalidatorTestBase::Now() {
   return clock_.Now();
 }
 
-int64_t CloudPolicyInvalidatorTest::V(int version) {
+int64_t CloudPolicyInvalidatorTestBase::V(int version) {
   return GetVersion(Now()) + version;
 }
 
-int64_t CloudPolicyInvalidatorTest::GetVersion(base::Time time) {
+int64_t CloudPolicyInvalidatorTestBase::GetVersion(base::Time time) {
   return (time - base::Time::UnixEpoch()).InMicroseconds();
 }
 
-em::DeviceRegisterRequest::Type
-CloudPolicyInvalidatorTest::GetPolicyType() const {
+em::DeviceRegisterRequest::Type CloudPolicyInvalidatorTestBase::GetPolicyType()
+    const {
   return UserCloudPolicyInvalidator::GetPolicyType();
 }
 
-bool CloudPolicyInvalidatorTest::CheckPolicyRefreshed(base::TimeDelta delay) {
+bool CloudPolicyInvalidatorTestBase::CheckPolicyRefreshed(
+    base::TimeDelta delay) {
   base::TimeDelta max_delay = delay + base::TimeDelta::FromMilliseconds(
       CloudPolicyInvalidator::kMaxFetchDelayMin);
 
@@ -421,7 +452,7 @@
   return CheckPolicyRefreshCount(1);
 }
 
-bool CloudPolicyInvalidatorTest::CheckPolicyRefreshCount(int count) {
+bool CloudPolicyInvalidatorTestBase::CheckPolicyRefreshCount(int count) {
   if (!client_) {
     task_runner_->RunUntilIdle();
     return count == 0;
@@ -439,13 +470,22 @@
   return testing::Mock::VerifyAndClearExpectations(client_);
 }
 
-const invalidation::ObjectId& CloudPolicyInvalidatorTest::GetPolicyObjectId(
+const invalidation::ObjectId& CloudPolicyInvalidatorTestBase::GetPolicyObjectId(
     PolicyObject object) const {
   EXPECT_TRUE(object == POLICY_OBJECT_A || object == POLICY_OBJECT_B);
   return object == POLICY_OBJECT_A ? object_id_a_ : object_id_b_;
 }
 
-TEST_F(CloudPolicyInvalidatorTest, Uninitialized) {
+class CloudPolicyInvalidatorTest : public CloudPolicyInvalidatorTestBase,
+                                   public testing::WithParamInterface<bool> {
+ protected:
+  CloudPolicyInvalidatorTest();
+};
+
+CloudPolicyInvalidatorTest::CloudPolicyInvalidatorTest()
+    : CloudPolicyInvalidatorTestBase(GetParam() /* is_fcm_enabled */) {}
+
+TEST_P(CloudPolicyInvalidatorTest, Uninitialized) {
   // No invalidations should be processed if the invalidator is not initialized.
   StartInvalidator(false, /* initialize */
                    true,  /* start_refresh_scheduler */
@@ -457,7 +497,7 @@
   EXPECT_EQ(0, GetHighestHandledInvalidationVersion());
 }
 
-TEST_F(CloudPolicyInvalidatorTest, RefreshSchedulerNotStarted) {
+TEST_P(CloudPolicyInvalidatorTest, RefreshSchedulerNotStarted) {
   // No invalidations should be processed if the refresh scheduler is not
   // started.
   StartInvalidator(true,  /* initialize */
@@ -470,7 +510,7 @@
   EXPECT_EQ(0, GetHighestHandledInvalidationVersion());
 }
 
-TEST_F(CloudPolicyInvalidatorTest, DisconnectCoreThenInitialize) {
+TEST_P(CloudPolicyInvalidatorTest, DisconnectCoreThenInitialize) {
   // No invalidations should be processed if the core is disconnected before
   // initialization.
   StartInvalidator(false, /* initialize */
@@ -485,7 +525,7 @@
   EXPECT_EQ(0, GetHighestHandledInvalidationVersion());
 }
 
-TEST_F(CloudPolicyInvalidatorTest, InitializeThenStartRefreshScheduler) {
+TEST_P(CloudPolicyInvalidatorTest, InitializeThenStartRefreshScheduler) {
   // Make sure registration occurs and invalidations are processed when
   // Initialize is called before starting the refresh scheduler.
   // Note that the reverse case (start refresh scheduler then initialize) is
@@ -503,7 +543,7 @@
   EXPECT_EQ(0, GetHighestHandledInvalidationVersion());
 }
 
-TEST_F(CloudPolicyInvalidatorTest, RegisterOnStoreLoaded) {
+TEST_P(CloudPolicyInvalidatorTest, RegisterOnStoreLoaded) {
   // No registration when store is not loaded.
   StartInvalidator();
   EXPECT_FALSE(IsInvalidatorRegistered());
@@ -531,7 +571,7 @@
   EXPECT_EQ(0, GetHighestHandledInvalidationVersion());
 }
 
-TEST_F(CloudPolicyInvalidatorTest, ChangeRegistration) {
+TEST_P(CloudPolicyInvalidatorTest, ChangeRegistration) {
   // Register for object A.
   StartInvalidator();
   StorePolicy(POLICY_OBJECT_A);
@@ -560,7 +600,7 @@
   EXPECT_EQ(0, GetHighestHandledInvalidationVersion());
 }
 
-TEST_F(CloudPolicyInvalidatorTest, UnregisterOnStoreLoaded) {
+TEST_P(CloudPolicyInvalidatorTest, UnregisterOnStoreLoaded) {
   // Register for object A.
   StartInvalidator();
   StorePolicy(POLICY_OBJECT_A);
@@ -589,7 +629,7 @@
   EXPECT_EQ(0, GetHighestHandledInvalidationVersion());
 }
 
-TEST_F(CloudPolicyInvalidatorTest, HandleInvalidation) {
+TEST_P(CloudPolicyInvalidatorTest, HandleInvalidation) {
   // Register and fire invalidation
   StorePolicy(POLICY_OBJECT_A);
   StartInvalidator();
@@ -611,7 +651,7 @@
   EXPECT_EQ(V(12), GetHighestHandledInvalidationVersion());
 }
 
-TEST_F(CloudPolicyInvalidatorTest, HandleInvalidationWithUnknownVersion) {
+TEST_P(CloudPolicyInvalidatorTest, HandleInvalidationWithUnknownVersion) {
   // Register and fire invalidation with unknown version.
   StorePolicy(POLICY_OBJECT_A);
   StartInvalidator();
@@ -631,7 +671,7 @@
   EXPECT_EQ(0, GetHighestHandledInvalidationVersion());
 }
 
-TEST_F(CloudPolicyInvalidatorTest, HandleMultipleInvalidations) {
+TEST_P(CloudPolicyInvalidatorTest, HandleMultipleInvalidations) {
   // Generate multiple invalidations.
   StorePolicy(POLICY_OBJECT_A);
   StartInvalidator();
@@ -663,7 +703,7 @@
   EXPECT_EQ(V(3), GetHighestHandledInvalidationVersion());
 }
 
-TEST_F(CloudPolicyInvalidatorTest,
+TEST_P(CloudPolicyInvalidatorTest,
        HandleMultipleInvalidationsWithUnknownVersion) {
   // Validate that multiple invalidations with unknown version each generate
   // unique invalidation version numbers.
@@ -697,7 +737,7 @@
   EXPECT_EQ(0, GetHighestHandledInvalidationVersion());
 }
 
-TEST_F(CloudPolicyInvalidatorTest,
+TEST_P(CloudPolicyInvalidatorTest,
        InitialHighestHandledInvalidationVersionNonZero) {
   StorePolicy(POLICY_OBJECT_A);
   StartInvalidator(true, /* initialize */
@@ -738,7 +778,7 @@
   EXPECT_EQ(V(3), GetHighestHandledInvalidationVersion());
 }
 
-TEST_F(CloudPolicyInvalidatorTest, AcknowledgeBeforeRefresh) {
+TEST_P(CloudPolicyInvalidatorTest, AcknowledgeBeforeRefresh) {
   // Generate an invalidation.
   StorePolicy(POLICY_OBJECT_A);
   StartInvalidator();
@@ -754,7 +794,7 @@
   EXPECT_EQ(V(3), GetHighestHandledInvalidationVersion());
 }
 
-TEST_F(CloudPolicyInvalidatorTest, NoCallbackAfterShutdown) {
+TEST_P(CloudPolicyInvalidatorTest, NoCallbackAfterShutdown) {
   // Generate an invalidation.
   StorePolicy(POLICY_OBJECT_A);
   StartInvalidator();
@@ -768,7 +808,7 @@
   DestroyInvalidator();
 }
 
-TEST_F(CloudPolicyInvalidatorTest, StateChanged) {
+TEST_P(CloudPolicyInvalidatorTest, StateChanged) {
   // Test invalidation service state changes while not registered.
   StartInvalidator();
   DisableInvalidationService();
@@ -806,7 +846,7 @@
   EXPECT_EQ(0, GetHighestHandledInvalidationVersion());
 }
 
-TEST_F(CloudPolicyInvalidatorTest, Disconnect) {
+TEST_P(CloudPolicyInvalidatorTest, Disconnect) {
   // Generate an invalidation.
   StorePolicy(POLICY_OBJECT_A);
   StartInvalidator();
@@ -846,9 +886,13 @@
   EXPECT_EQ(0, GetHighestHandledInvalidationVersion());
 }
 
+INSTANTIATE_TEST_SUITE_P(FCMEnabledAndFCMDisabled,
+                         CloudPolicyInvalidatorTest,
+                         testing::Bool() /* is_fcm_enabled */);
+
 class CloudPolicyInvalidatorUserTypedTest
-    : public CloudPolicyInvalidatorTest,
-      public testing::WithParamInterface<em::DeviceRegisterRequest::Type>  {
+    : public CloudPolicyInvalidatorTestBase,
+      public testing::WithParamInterface<TestParams> {
  protected:
   CloudPolicyInvalidatorUserTypedTest();
   virtual ~CloudPolicyInvalidatorUserTypedTest();
@@ -877,8 +921,8 @@
   DISALLOW_COPY_AND_ASSIGN(CloudPolicyInvalidatorUserTypedTest);
 };
 
-CloudPolicyInvalidatorUserTypedTest::CloudPolicyInvalidatorUserTypedTest() {
-}
+CloudPolicyInvalidatorUserTypedTest::CloudPolicyInvalidatorUserTypedTest()
+    : CloudPolicyInvalidatorTestBase(GetParam().is_fcm_enabled) {}
 
 CloudPolicyInvalidatorUserTypedTest::~CloudPolicyInvalidatorUserTypedTest() {
 }
@@ -911,7 +955,7 @@
 
 em::DeviceRegisterRequest::Type
 CloudPolicyInvalidatorUserTypedTest::GetPolicyType() const {
-  return GetParam();
+  return GetParam().policy_type;
 }
 
 std::unique_ptr<base::HistogramSamples>
@@ -1084,19 +1128,32 @@
 }
 
 #if defined(OS_CHROMEOS)
-INSTANTIATE_TEST_SUITE_P(CloudPolicyInvalidatorUserTypedTestInstance,
-                         CloudPolicyInvalidatorUserTypedTest,
-                         testing::Values(em::DeviceRegisterRequest::USER,
-                                         em::DeviceRegisterRequest::DEVICE));
+INSTANTIATE_TEST_SUITE_P(
+    CloudPolicyInvalidatorUserTypedTestInstance,
+    CloudPolicyInvalidatorUserTypedTest,
+    testing::Values(
+        TestParams(false /* is_fcm_enabled */, em::DeviceRegisterRequest::USER),
+        TestParams(true /* is_fcm_enabled */, em::DeviceRegisterRequest::USER),
+        TestParams(false /* is_fcm_enabled */,
+                   em::DeviceRegisterRequest::DEVICE),
+        TestParams(true /* is_fcm_enabled */,
+                   em::DeviceRegisterRequest::DEVICE)));
 #elif defined(OS_ANDROID)
 INSTANTIATE_TEST_SUITE_P(
     CloudPolicyInvalidatorUserTypedTestInstance,
     CloudPolicyInvalidatorUserTypedTest,
-    testing::Values(em::DeviceRegisterRequest::ANDROID_BROWSER));
+    testing::Values(TestParams(false /* is_fcm_enabled */,
+                               em::DeviceRegisterRequest::ANDROID_BROWSER),
+                    TestParams(true /* is_fcm_enabled */,
+                               em::DeviceRegisterRequest::ANDROID_BROWSER)));
 #else
-INSTANTIATE_TEST_SUITE_P(CloudPolicyInvalidatorUserTypedTestInstance,
-                         CloudPolicyInvalidatorUserTypedTest,
-                         testing::Values(em::DeviceRegisterRequest::BROWSER));
+INSTANTIATE_TEST_SUITE_P(
+    CloudPolicyInvalidatorUserTypedTestInstance,
+    CloudPolicyInvalidatorUserTypedTest,
+    testing::Values(TestParams(false /* is_fcm_enabled */,
+                               em::DeviceRegisterRequest::BROWSER),
+                    TestParams(true /* is_fcm_enabled */,
+                               em::DeviceRegisterRequest::BROWSER)));
 #endif
 
 }  // namespace policy
diff --git a/chrome/browser/policy/cloud/user_cloud_policy_invalidator.cc b/chrome/browser/policy/cloud/user_cloud_policy_invalidator.cc
index ac26099..8bc8e5b4 100644
--- a/chrome/browser/policy/cloud/user_cloud_policy_invalidator.cc
+++ b/chrome/browser/policy/cloud/user_cloud_policy_invalidator.cc
@@ -12,21 +12,41 @@
 #include "build/build_config.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/invalidation/deprecated_profile_invalidation_provider_factory.h"
+#include "chrome/browser/invalidation/profile_invalidation_provider_factory.h"
 #include "components/invalidation/impl/profile_invalidation_provider.h"
 #include "components/policy/core/common/cloud/cloud_policy_manager.h"
 #include "content/public/browser/notification_source.h"
 
+namespace {
+
+invalidation::ProfileInvalidationProvider* GetInvalidationProvider(
+    Profile* profile,
+    bool is_fcm_enabled) {
+  if (is_fcm_enabled) {
+    return invalidation::ProfileInvalidationProviderFactory::GetForProfile(
+        profile);
+  }
+  return invalidation::DeprecatedProfileInvalidationProviderFactory::
+      GetForProfile(profile);
+}
+
+}  // namespace
+
 namespace policy {
 
 UserCloudPolicyInvalidator::UserCloudPolicyInvalidator(
     Profile* profile,
-    CloudPolicyManager* policy_manager)
+    CloudPolicyManager* policy_manager,
+    bool is_fcm_enabled,
+    std::string fcm_sender_id)
     : CloudPolicyInvalidator(GetPolicyType(),
                              policy_manager->core(),
                              base::ThreadTaskRunnerHandle::Get(),
                              base::DefaultClock::GetInstance(),
-                             0 /* highest_handled_invalidation_version */),
-      profile_(profile) {
+                             0 /* highest_handled_invalidation_version */,
+                             is_fcm_enabled),
+      profile_(profile),
+      fcm_sender_id_(std::move(fcm_sender_id)) {
   DCHECK(profile);
 
   // Register for notification that profile creation is complete. The
@@ -65,10 +85,15 @@
   // service can safely be initialized.
   DCHECK_EQ(chrome::NOTIFICATION_PROFILE_ADDED, type);
   invalidation::ProfileInvalidationProvider* invalidation_provider =
-      invalidation::DeprecatedProfileInvalidationProviderFactory::GetForProfile(
-          profile_);
-  if (invalidation_provider)
+      GetInvalidationProvider(profile_, is_fcm_enabled());
+  if (!invalidation_provider)
+    return;
+  if (is_fcm_enabled()) {
+    Initialize(invalidation_provider->GetInvalidationServiceForCustomSender(
+        fcm_sender_id_));
+  } else {
     Initialize(invalidation_provider->GetInvalidationService());
+  }
 }
 
 }  // namespace policy
diff --git a/chrome/browser/policy/cloud/user_cloud_policy_invalidator.h b/chrome/browser/policy/cloud/user_cloud_policy_invalidator.h
index 0a69c0f..038e69d 100644
--- a/chrome/browser/policy/cloud/user_cloud_policy_invalidator.h
+++ b/chrome/browser/policy/cloud/user_cloud_policy_invalidator.h
@@ -5,6 +5,8 @@
 #ifndef CHROME_BROWSER_POLICY_CLOUD_USER_CLOUD_POLICY_INVALIDATOR_H_
 #define CHROME_BROWSER_POLICY_CLOUD_USER_CLOUD_POLICY_INVALIDATOR_H_
 
+#include <string>
+
 #include "base/macros.h"
 #include "chrome/browser/policy/cloud/cloud_policy_invalidator.h"
 #include "components/keyed_service/core/keyed_service.h"
@@ -28,10 +30,13 @@
   // a reference to the profile's invalidation service. Both the profile and
   // invalidation service must remain valid until Shutdown is called.
   // |policy_manager| is the policy manager for the user policy and must remain
-  // valid until Shutdown is called.
-  UserCloudPolicyInvalidator(
-      Profile* profile,
-      CloudPolicyManager* policy_manager);
+  // valid until Shutdown is called. |is_fcm_enabled| is the flag denoting
+  // FCM is being used as InvalidationService. |fcm_sender_id| is the id of
+  // FCM Policy Invalidation sender coming from Firebase console.
+  UserCloudPolicyInvalidator(Profile* profile,
+                             CloudPolicyManager* policy_manager,
+                             bool is_fcm_enabled,
+                             std::string fcm_sender_id);
 
   static enterprise_management::DeviceRegisterRequest::Type GetPolicyType();
 
@@ -50,6 +55,9 @@
   // Used to register for notification that profile creation is complete.
   content::NotificationRegistrar registrar_;
 
+  // Sender ID coming from the Firebase console.
+  const std::string fcm_sender_id_;
+
   DISALLOW_COPY_AND_ASSIGN(UserCloudPolicyInvalidator);
 };
 
diff --git a/chrome/browser/policy/cloud/user_cloud_policy_invalidator_factory.cc b/chrome/browser/policy/cloud/user_cloud_policy_invalidator_factory.cc
index 87d71f6..1e782098 100644
--- a/chrome/browser/policy/cloud/user_cloud_policy_invalidator_factory.cc
+++ b/chrome/browser/policy/cloud/user_cloud_policy_invalidator_factory.cc
@@ -4,11 +4,15 @@
 
 #include "chrome/browser/policy/cloud/user_cloud_policy_invalidator_factory.h"
 
+#include "base/feature_list.h"
 #include "build/build_config.h"
 #include "chrome/browser/invalidation/deprecated_profile_invalidation_provider_factory.h"
+#include "chrome/browser/invalidation/profile_invalidation_provider_factory.h"
 #include "chrome/browser/policy/cloud/user_cloud_policy_invalidator.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/common/chrome_features.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
 #if defined(OS_CHROMEOS)
 #include "chrome/browser/chromeos/policy/user_cloud_policy_manager_chromeos.h"
 #else
@@ -29,6 +33,7 @@
           BrowserContextDependencyManager::GetInstance()) {
   DependsOn(invalidation::DeprecatedProfileInvalidationProviderFactory::
                 GetInstance());
+  DependsOn(invalidation::ProfileInvalidationProviderFactory::GetInstance());
 }
 
 UserCloudPolicyInvalidatorFactory::~UserCloudPolicyInvalidatorFactory() {}
@@ -45,7 +50,10 @@
   if (!policy_manager)
     return NULL;
 
-  return new UserCloudPolicyInvalidator(profile, policy_manager);
+  return new UserCloudPolicyInvalidator(
+      profile, policy_manager,
+      base::FeatureList::IsEnabled(features::kPolicyFcmInvalidations),
+      policy::kPolicyFCMInvalidationSenderID);
 }
 
 bool UserCloudPolicyInvalidatorFactory::
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index ab4c61d..21457c6 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -610,9 +610,6 @@
   { key::kStickyKeysEnabled,
     ash::prefs::kAccessibilityStickyKeysEnabled,
     base::Value::Type::BOOLEAN },
-  { key::kDockedMagnifierEnabled,
-    ash::prefs::kDockedMagnifierEnabled,
-    base::Value::Type::BOOLEAN },
   { key::kDeviceLoginScreenDefaultLargeCursorEnabled,
     nullptr,
     base::Value::Type::BOOLEAN },
@@ -1409,7 +1406,7 @@
       key::kUptimeLimit, prefs::kUptimeLimit, 3600, INT_MAX, true));
   handlers->AddHandler(std::make_unique<IntRangePolicyHandler>(
       key::kDeviceLoginScreenDefaultScreenMagnifierType, nullptr,
-      chromeos::MAGNIFIER_DISABLED, chromeos::MAGNIFIER_FULL, false));
+      chromeos::MAGNIFIER_DISABLED, chromeos::MAGNIFIER_DOCKED, false));
   // TODO(binjin): Remove LegacyPoliciesDeprecatingPolicyHandler for these two
   // policies once deprecation of legacy power management policies is done.
   // http://crbug.com/346229
diff --git a/chrome/browser/policy/policy_browsertest.cc b/chrome/browser/policy/policy_browsertest.cc
index 17d06fe..41d2cf6 100644
--- a/chrome/browser/policy/policy_browsertest.cc
+++ b/chrome/browser/policy/policy_browsertest.cc
@@ -3940,6 +3940,9 @@
   // Verify that the screen magnifier cannot be enabled manually anymore.
   magnification_manager->SetMagnifierEnabled(true);
   EXPECT_FALSE(magnification_manager->IsMagnifierEnabled());
+  // Verify that the docked magnifier cannot be enabled manually anymore.
+  magnification_manager->SetDockedMagnifierEnabled(true);
+  EXPECT_FALSE(magnification_manager->IsDockedMagnifierEnabled());
 }
 
 IN_PROC_BROWSER_TEST_F(PolicyTest, ScreenMagnifierTypeFull) {
@@ -3964,6 +3967,29 @@
   EXPECT_TRUE(magnification_manager->IsMagnifierEnabled());
 }
 
+IN_PROC_BROWSER_TEST_F(PolicyTest, ScreenMagnifierTypeDocked) {
+  // Verifies that the docked magnifier accessibility feature can be
+  // controlled through policy.
+  chromeos::MagnificationManager* magnification_manager =
+      chromeos::MagnificationManager::Get();
+
+  // Verify that the docked magnifier is initially disabled
+  EXPECT_FALSE(magnification_manager->IsDockedMagnifierEnabled());
+
+  // Verify that policy overrides the manual setting.
+  PolicyMap policies;
+  policies.Set(key::kScreenMagnifierType, POLICY_LEVEL_MANDATORY,
+               POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
+               std::make_unique<base::Value>(chromeos::MAGNIFIER_DOCKED),
+               nullptr);
+  UpdateProviderPolicy(policies);
+  EXPECT_TRUE(magnification_manager->IsDockedMagnifierEnabled());
+
+  // Verify that the docked magnifier cannot be disabled manually anymore.
+  magnification_manager->SetDockedMagnifierEnabled(false);
+  EXPECT_TRUE(magnification_manager->IsDockedMagnifierEnabled());
+}
+
 IN_PROC_BROWSER_TEST_F(PolicyTest, AccessibilityVirtualKeyboardEnabled) {
   // Verifies that the on-screen keyboard accessibility feature can be
   // controlled through policy.
@@ -4013,31 +4039,6 @@
   EXPECT_FALSE(accessibility_manager->IsStickyKeysEnabled());
 }
 
-IN_PROC_BROWSER_TEST_F(PolicyTest, DockedMagnifierEnabled) {
-  // Verifies that the docked magnifier accessibility feature can be
-  // controlled through policy.
-  chromeos::MagnificationManager* magnification_manager =
-      chromeos::MagnificationManager::Get();
-
-  // Verify that the docked magnifier is initially disabled
-  EXPECT_FALSE(magnification_manager->IsDockedMagnifierEnabled());
-
-  // Manually enable the docked magnifier.
-  magnification_manager->SetDockedMagnifierEnabled(true);
-  EXPECT_TRUE(magnification_manager->IsDockedMagnifierEnabled());
-
-  // Verify that policy overrides the manual setting.
-  PolicyMap policies;
-  policies.Set(key::kDockedMagnifierEnabled, POLICY_LEVEL_MANDATORY,
-               POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
-               std::make_unique<base::Value>(false), nullptr);
-  UpdateProviderPolicy(policies);
-  EXPECT_FALSE(magnification_manager->IsDockedMagnifierEnabled());
-
-  // Verify that the docked magnifier cannot be enabled manually anymore.
-  magnification_manager->SetDockedMagnifierEnabled(true);
-  EXPECT_FALSE(magnification_manager->IsDockedMagnifierEnabled());
-}
 
 IN_PROC_BROWSER_TEST_F(PolicyTest, VirtualKeyboardEnabled) {
   auto* keyboard_client = ChromeKeyboardControllerClient::Get();
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index fc3b400..2d69e32 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -62,6 +62,7 @@
 #include "chrome/browser/rlz/chrome_rlz_tracker_delegate.h"
 #include "chrome/browser/search/local_ntp_first_run_field_trial_handler.h"
 #include "chrome/browser/search/search.h"
+#include "chrome/browser/sharing/sharing_sync_preference.h"
 #include "chrome/browser/ssl/chrome_ssl_host_state_delegate.h"
 #include "chrome/browser/ssl/ssl_config_service_manager.h"
 #include "chrome/browser/task_manager/task_manager_interface.h"
@@ -301,6 +302,7 @@
 #include "chromeos/services/multidevice_setup/multidevice_setup_service.h"
 #include "chromeos/timezone/timezone_resolver.h"
 #include "components/arc/arc_prefs.h"
+#include "components/invalidation/impl/fcm_invalidation_service.h"
 #include "components/invalidation/impl/invalidator_storage.h"
 #include "components/onc/onc_pref_names.h"
 #include "components/quirks/quirks_manager.h"
@@ -618,6 +620,7 @@
   extensions::ExtensionAssetsManagerChromeOS::RegisterPrefs(registry);
   extensions::lock_screen_data::LockScreenItemStorage::RegisterLocalState(
       registry);
+  invalidation::FCMInvalidationService::RegisterPrefs(registry);
   invalidation::InvalidatorStorage::RegisterPrefs(registry);
   ::onc::RegisterPrefs(registry);
   policy::AutoEnrollmentClientImpl::RegisterPrefs(registry);
@@ -631,6 +634,8 @@
   policy::WebUsbAllowDevicesForUrlsPolicyHandler::RegisterPrefs(registry);
   quirks::QuirksManager::RegisterPrefs(registry);
   UpgradeDetectorChromeos::RegisterPrefs(registry);
+  syncer::PerUserTopicRegistrationManager::RegisterPrefs(registry);
+  syncer::InvalidatorRegistrarWithMemory::RegisterPrefs(registry);
 #endif
 
 #if defined(OS_MACOSX)
@@ -719,6 +724,7 @@
   safe_browsing::RegisterProfilePrefs(registry);
   SafeBrowsingTriggeredPopupBlocker::RegisterProfilePrefs(registry);
   SessionStartupPref::RegisterProfilePrefs(registry);
+  SharingSyncPreference::RegisterProfilePrefs(registry);
   sync_sessions::SessionSyncPrefs::RegisterProfilePrefs(registry);
   syncer::SyncPrefs::RegisterProfilePrefs(registry);
   syncer::PerUserTopicRegistrationManager::RegisterProfilePrefs(registry);
diff --git a/chrome/browser/resources/chromeos/add_supervision/add_supervision.html b/chrome/browser/resources/chromeos/add_supervision/add_supervision.html
index 55ac2da2..a1d0917 100644
--- a/chrome/browser/resources/chromeos/add_supervision/add_supervision.html
+++ b/chrome/browser/resources/chromeos/add_supervision/add_supervision.html
@@ -17,6 +17,8 @@
       html,
       body {
         height: 100%;
+        overflow: auto;
+        overflow-y: hidden;
       }
     </style>
   </head>
diff --git a/chrome/browser/resources/chromeos/login/cr_ui.js b/chrome/browser/resources/chromeos/login/cr_ui.js
index 81edcba..07ee6f21 100644
--- a/chrome/browser/resources/chromeos/login/cr_ui.js
+++ b/chrome/browser/resources/chromeos/login/cr_ui.js
@@ -342,11 +342,10 @@
    * attribute screen if it's present.
    */
   Oobe.isEnrollmentSuccessfulForTest = function() {
-    if (document.querySelector('.oauth-enroll-state-attribute-prompt'))
+    if ($('enterprise-enrollment').$$('.oauth-enroll-state-attribute-prompt'))
       chrome.send('oauthEnrollAttributes', ['', '']);
 
-    return $('oauth-enrollment')
-        .classList.contains('oauth-enroll-state-success');
+    return !!$('enterprise-enrollment').$$('.oauth-enroll-state-success');
   };
 
   /**
diff --git a/chrome/browser/resources/chromeos/login/custom_elements_login.html b/chrome/browser/resources/chromeos/login/custom_elements_login.html
index 0770d227..280f17d 100644
--- a/chrome/browser/resources/chromeos/login/custom_elements_login.html
+++ b/chrome/browser/resources/chromeos/login/custom_elements_login.html
@@ -24,6 +24,8 @@
 <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="enterprise_enrollment.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 a400a60..93c1d26 100644
--- a/chrome/browser/resources/chromeos/login/custom_elements_login.js
+++ b/chrome/browser/resources/chromeos/login/custom_elements_login.js
@@ -39,3 +39,5 @@
 // <include src="../assistant_optin/assistant_optin_flow.js">
 // <include src="multidevice_setup_first_run.js">
 // <include src="supervision_onboarding.js">
+// <include src="enrollment_license_card.js">
+// <include src="enterprise_enrollment.js">
diff --git a/chrome/browser/resources/chromeos/login/custom_elements_oobe.html b/chrome/browser/resources/chromeos/login/custom_elements_oobe.html
index 4e7ca93..618f0cc 100644
--- a/chrome/browser/resources/chromeos/login/custom_elements_oobe.html
+++ b/chrome/browser/resources/chromeos/login/custom_elements_oobe.html
@@ -34,6 +34,7 @@
 <include src="active_directory_password_change.html">
 <include src="arc_terms_of_service.html">
 <include src="enrollment_license_card.html">
+<include src="enterprise_enrollment.html">
 <include src="sync_consent.html">
 <include src="fingerprint_setup.html">
 <include src="demo_setup.html">
diff --git a/chrome/browser/resources/chromeos/login/custom_elements_oobe.js b/chrome/browser/resources/chromeos/login/custom_elements_oobe.js
index 7eeeca0f..b91be2ea 100644
--- a/chrome/browser/resources/chromeos/login/custom_elements_oobe.js
+++ b/chrome/browser/resources/chromeos/login/custom_elements_oobe.js
@@ -45,6 +45,7 @@
 // <include src="arc_terms_of_service.js">
 // <include src="oobe_supervision_transition.js">
 // <include src="enrollment_license_card.js">
+// <include src="enterprise_enrollment.js">
 // <include src="sync_consent.js">
 // <include src="fingerprint_setup.js">
 // <include src="demo_setup.js">
diff --git a/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.css b/chrome/browser/resources/chromeos/login/enterprise_enrollment.css
similarity index 93%
rename from chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.css
rename to chrome/browser/resources/chromeos/login/enterprise_enrollment.css
index cd1296b..d532e10f 100644
--- a/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.css
+++ b/chrome/browser/resources/chromeos/login/enterprise_enrollment.css
@@ -2,16 +2,18 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file. */
 
-#oauth-enrollment {
-  height: 640px;
+:host {
   padding: 0;
-  width: 768px;
 }
 
-#oauth-enrollment.saml {
+:host(.saml) #oauth-enroll-step-contents {
   padding-top: 44px;
 }
 
+:host(:not(.saml)) #oauth-saml-notice-container {
+  display: none;
+}
+
 #oauth-enroll-step-contents {
   color: #666;
   height: 100%;
@@ -91,10 +93,6 @@
   top: 0;
 }
 
-#oauth-enrollment:not(.saml) #oauth-saml-notice-container {
-  display: none;
-}
-
 #oauth-saml-notice-message {
   color: rgb(106, 106, 106);
   font-size: 13px;
diff --git a/chrome/browser/resources/chromeos/login/enterprise_enrollment.html b/chrome/browser/resources/chromeos/login/enterprise_enrollment.html
new file mode 100644
index 0000000..3db3a2a
--- /dev/null
+++ b/chrome/browser/resources/chromeos/login/enterprise_enrollment.html
@@ -0,0 +1,140 @@
+<!-- Copyright 2015 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. -->
+
+<link rel="import" href="chrome://oobe/custom_elements.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/iron-iconset-svg/iron-iconset-svg.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
+<link rel="stylesheet" href="gaia_card_parameters.css">
+
+<dom-module id="enterprise-enrollment">
+  <template>
+    <div id="oauth-enroll-step-contents">
+      <link rel="stylesheet" href="oobe_flex_layout.css">
+      <link rel="stylesheet" href="oobe_dialog_host.css">
+      <link rel="stylesheet" href="enterprise_enrollment.css">
+      <div id="oauth-enroll-step-signin">
+        <oobe-dialog class="gaia-dialog" role="dialog"
+                     id="enrollment-gaia-dialog" has-buttons no-header
+                     no-footer-padding>
+          <div slot="footer" class="flex layout vertical">
+            <webview id="oauth-enroll-auth-view"
+                     name="oauth-enroll-auth-view">
+            </webview>
+          </div>
+          <div slot="bottom-buttons"
+               class="flex layout horizontal center self-start">
+            <oobe-back-button id="oobe-signin-back-button"></oobe-back-button>
+          </div>
+        </oobe-dialog>
+      </div>
+      <div id="oauth-enroll-step-working">
+          <oobe-dialog id="oauth-enroll-working">
+            <hd-iron-icon slot="oobe-icon"
+                          icon1x="oobe-enrollment-32:briefcase"
+                          icon2x="oobe-enrollment-64:briefcase">
+            </hd-iron-icon>
+            <h1 slot="title" i18n-content="oauthEnrollScreenTitle"></h1>
+            <paper-progress slot="progress" indeterminate>
+            </paper-progress>
+
+            <div slot="footer" class="flex layout vertical" role="alert">
+              <div class="oauth-enroll-step-message">
+                <span i18n-content="oauthEnrollWorking"></span>
+              </div>
+            </div>
+          </oobe-dialog>
+      </div>
+      <div id="oauth-enroll-step-license">
+        <enrollment-license-card id="oauth-enroll-license-ui"
+                                 i18n-values="button-text:oauthEnrollNextBtn">
+        </enrollment-license-card>
+      </div>
+      <div id="oauth-enroll-step-ad-join">
+        <offline-ad-login id="oauth-enroll-ad-join-ui" is-domain-join
+            class="fit" i18n-values=
+                "ad-welcome-message:oauthEnrollAdDomainJoinWelcomeMessage">
+        </offline-ad-login>
+      </div>
+      <div id="oauth-enroll-step-error" role="alert">
+        <notification-card id="oauth-enroll-error-card" type="fail"
+            i18n-values="button-label:oauthEnrollRetry">
+        </notification-card>
+      </div>
+      <div id="oauth-enroll-step-success" role="alert">
+        <oobe-dialog id="oauth-enroll-success-card" has-buttons>
+          <hd-iron-icon slot="oobe-icon"
+                        icon1x="oobe-enrollment-32:briefcase"
+                        icon2x="oobe-enrollment-64:briefcase"></hd-iron-icon>
+          <h1 slot="title" i18n-content="oauthEnrollSuccessTitle"></h1>
+          <oobe-enrollment-success-with-domain
+              id="oauth-enroll-success-subtitle"
+              slot="subtitle">
+          </oobe-enrollment-success-with-domain>
+          <div slot="footer" class="flex layout vertical center end-justified">
+            <img srcset="images/enrollment_success_illustration_1x.png 1x,
+                    images/enrollment_success_illustration_2x.png 2x"
+                 i18n-values="alt:enrollmentSuccessIllustrationTitle">
+          </div>
+          <div slot="bottom-buttons" class="layout horizontal end-justified">
+            <oobe-text-button inverse id="enroll-success-done-button"
+                class="focus-on-show">
+              <div i18n-content="oauthEnrollDone"></div>
+            </oobe-text-button>
+          </div>
+        </oobe-dialog>
+      </div>
+      <div id="oauth-enroll-step-attribute-prompt">
+        <oobe-dialog id="oauth-enroll-attribute-prompt-card" has-buttons>
+          <hd-iron-icon slot="oobe-icon"
+                        icon1x="oobe-enrollment-32:briefcase"
+                        icon2x="oobe-enrollment-64:briefcase"></hd-iron-icon>
+          <h1 slot="title" i18n-content="oauthEnrollScreenTitle"></h1>
+          <div slot="subtitle" i18n-content="oauthEnrollDeviceInformation">
+          </div>
+          <div slot="footer" class="layout vertical start">
+            <div class="oauth-enroll-step-message">
+                      <span id="oauth-enroll-attribute-prompt-message"
+                            i18n-content="oauthEnrollAttributeExplanation">
+                      </span>
+              <a href="#" id="oauth-enroll-learn-more-link"
+                 class="oauth-enroll-link"
+                 i18n-content="oauthEnrollExplainAttributeLink"></a>
+            </div>
+            <gaia-input id="oauth-enroll-asset-id" type="text"
+                        class="focus-on-show">
+              <div slot="label" i18n-content="oauthEnrollAssetIdLabel">
+              </div>
+            </gaia-input>
+            <gaia-input id="oauth-enroll-location" type="text">
+              <div slot="label" i18n-content="oauthEnrollLocationLabel">
+              </div>
+            </gaia-input>
+          </div>
+          <div slot="bottom-buttons" class="layout horizontal end-justified">
+            <oobe-text-button id="enroll-attributes-skip-button">
+              <div i18n-content="oauthEnrollSkip"></div>
+            </oobe-text-button>
+            <div class="flex"></div>
+            <oobe-next-button
+                id="enroll-attributes-submit-button"></oobe-next-button>
+          </div>
+        </oobe-dialog>
+      </div>
+      <div id="oauth-enroll-step-attribute-prompt-error">
+        <notification-card id="oauth-enroll-attribute-prompt-error-card"
+            type="fail" i18n-values="button-label:oauthEnrollDone">
+        </notification-card>
+      </div>
+      <div id="oauth-enroll-step-active-directory-join-error">
+        <notification-card id="oauth-enroll-active-directory-join-error-card"
+            type="fail" i18n-values="button-label:oauthEnrollRetry">
+        </notification-card>
+      </div>
+    </div>
+    <div id="oauth-saml-notice-container">
+      <span id="oauth-saml-notice-message"></span>
+    </div>
+    <navigation-bar id="oauth-enroll-navigation"></navigation-bar>
+  </template>
+</dom-module>
diff --git a/chrome/browser/resources/chromeos/login/enterprise_enrollment.js b/chrome/browser/resources/chromeos/login/enterprise_enrollment.js
new file mode 100644
index 0000000..cca50da
--- /dev/null
+++ b/chrome/browser/resources/chromeos/login/enterprise_enrollment.js
@@ -0,0 +1,551 @@
+// Copyright 2015 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.
+
+/**
+ * @fileoverview Polymer element for Enterprise Enrollment screen.
+ */
+
+/* Code which is embedded inside of the webview. See below for details.
+/** @const */
+var INJECTED_WEBVIEW_SCRIPT = String.raw`
+                    (function() {
+                       // <include src="../keyboard/keyboard_utils.js">
+                       keyboard.initializeKeyboardFlow(true);
+                     })();`;
+
+/** @const */ var ENROLLMENT_STEP = {
+  SIGNIN: 'signin',
+  AD_JOIN: 'ad-join',
+  LICENSE_TYPE: 'license',
+  WORKING: 'working',
+  ATTRIBUTE_PROMPT: 'attribute-prompt',
+  ERROR: 'error',
+  SUCCESS: 'success',
+
+  /* TODO(dzhioev): define this step on C++ side.
+   */
+  ATTRIBUTE_PROMPT_ERROR: 'attribute-prompt-error',
+  ACTIVE_DIRECTORY_JOIN_ERROR: 'active-directory-join-error',
+};
+
+Polymer({
+  is: 'enterprise-enrollment',
+
+  behaviors: [I18nBehavior, OobeDialogHostBehavior],
+
+  properties: {
+    /**
+     * Reference to OOBE screen object.
+     * @type {!{
+     *     onAuthFrameLoaded_: function(),
+     *     onAuthCompleted_: function(string),
+     *     onAdCompleteLogin_: function(string, string, string, string, string),
+     *     onAdUnlockConfiguration_: function(string),
+     *     onLicenseTypeSelected_: function(string),
+     *     closeEnrollment_: function(string),
+     *     onAttributesEntered_: function(string, string),
+     * }}
+     */
+    screen: {
+      type: Object,
+    },
+  },
+
+  /**
+   * Authenticator object that wraps GAIA webview.
+   */
+  authenticator_: null,
+
+  /**
+   * The current step. This is the last value passed to showStep().
+   */
+  currentStep_: null,
+
+  /**
+   * We block esc, back button and cancel button until gaia is loaded to
+   * prevent multiple cancel events.
+   */
+  isCancelDisabled_: null,
+
+  get isCancelDisabled() {
+    return this.isCancelDisabled_;
+  },
+  set isCancelDisabled(disabled) {
+    this.isCancelDisabled_ = disabled;
+  },
+
+  isManualEnrollment_: undefined,
+
+  /**
+   * An element containing UI for picking license type.
+   * @type {EnrollmentLicenseCard}
+   * @private
+   */
+  licenseUi_: undefined,
+
+  /**
+   * An element containing navigation buttons.
+   */
+  navigation_: undefined,
+
+  /**
+   * An element containing UI to join an AD domain.
+   * @type {OfflineAdLoginElement}
+   * @private
+   */
+  offlineAdUi_: undefined,
+
+  /**
+   * Value contained in the last received 'backButton' event.
+   * @type {boolean}
+   * @private
+   */
+  lastBackMessageValue_: false,
+
+  ready: function() {
+    this.navigation_ = this.$['oauth-enroll-navigation'];
+    this.offlineAdUi_ = this.$['oauth-enroll-ad-join-ui'];
+    this.licenseUi_ = this.$['oauth-enroll-license-ui'];
+
+    let authView = this.$['oauth-enroll-auth-view'];
+    this.authenticator_ = new cr.login.Authenticator(authView);
+
+    // Establish an initial messaging between content script and
+    // host script so that content script can message back.
+    authView.addEventListener('loadstop', function(e) {
+      e.target.contentWindow.postMessage(
+          'initialMessage', authView.src);
+    });
+
+    // When we get the advancing focus command message from injected content
+    // script, we can execute it on host script context.
+    window.addEventListener('message', function(e) {
+      if (e.data == 'forwardFocus')
+        keyboard.onAdvanceFocus(false);
+      else if (e.data == 'backwardFocus')
+        keyboard.onAdvanceFocus(true);
+    });
+
+    this.authenticator_.addEventListener(
+        'ready', (function() {
+                   if (this.currentStep_ != ENROLLMENT_STEP.SIGNIN)
+                     return;
+                   this.isCancelDisabled = false;
+                   this.screen.onAuthFrameLoaded_();
+                 }).bind(this));
+
+    this.authenticator_.addEventListener(
+        'authCompleted',
+        (function(e) {
+          var detail = e.detail;
+          if (!detail.email) {
+            this.showError(
+                loadTimeData.getString('fatalEnrollmentError'), false);
+            return;
+          }
+          this.screen.onAuthCompleted_(detail.email);
+        }).bind(this));
+
+    this.offlineAdUi_.addEventListener('authCompleted', function(e) {
+      this.offlineAdUi_.disabled = true;
+      this.offlineAdUi_.loading = true;
+      this.screen.onAdCompleteLogin_(
+        e.detail.machine_name,
+        e.detail.distinguished_name,
+        e.detail.encryption_types,
+        e.detail.username,
+        e.detail.password);
+    }.bind(this));
+    this.offlineAdUi_.addEventListener('unlockPasswordEntered', function(e) {
+      this.offlineAdUi_.disabled = true;
+      this.screen.onAdUnlockConfiguration_(e.detail.unlock_password);
+    }.bind(this));
+
+    this.authenticator_.addEventListener(
+        'authFlowChange', (function(e) {
+                            var isSAML = this.authenticator_.authFlow ==
+                                cr.login.Authenticator.AuthFlow.SAML;
+                            if (isSAML) {
+                              this.$['oauth-saml-notice-message'].textContent =
+                                  loadTimeData.getStringF(
+                                      'samlNotice',
+                                      this.authenticator_.authDomain);
+                            }
+                            this.classList.toggle('saml', isSAML);
+                            if (Oobe.getInstance().currentScreen == this)
+                              Oobe.getInstance().updateScreenSize(this);
+                            this.lastBackMessageValue_ = false;
+                            this.updateControlsState();
+                          }).bind(this));
+
+    this.authenticator_.addEventListener(
+        'backButton', (function(e) {
+                        this.lastBackMessageValue_ = !!e.detail;
+                        this.$['oauth-enroll-auth-view'].focus();
+                        this.updateControlsState();
+                      }).bind(this));
+
+    this.authenticator_.addEventListener(
+        'dialogShown', (function(e) {
+                         this.navigation_.disabled = true;
+                         this.$['oobe-signin-back-button'].disabled = true;
+                         // TODO(alemate): update the visual style.
+                       }).bind(this));
+
+    this.authenticator_.addEventListener(
+        'dialogHidden', (function(e) {
+                          this.navigation_.disabled = false;
+                          this.$['oobe-signin-back-button'].disabled = false;
+                          // TODO(alemate): update the visual style.
+                        }).bind(this));
+
+    this.authenticator_.insecureContentBlockedCallback =
+        (function(url) {
+          this.showError(
+              loadTimeData.getStringF('insecureURLEnrollmentError', url),
+              false);
+        }).bind(this);
+
+    this.authenticator_.missingGaiaInfoCallback =
+        (function() {
+          this.showError(
+              loadTimeData.getString('fatalEnrollmentError'), false);
+        }).bind(this);
+
+    this.$['oauth-enroll-error-card']
+        .addEventListener('buttonclick', this.doRetry_.bind(this));
+
+    this.$['oauth-enroll-attribute-prompt-error-card']
+        .addEventListener(
+            'buttonclick', this.onEnrollmentFinished_.bind(this));
+
+    this.$['enroll-success-done-button']
+        .addEventListener('tap', this.onEnrollmentFinished_.bind(this));
+
+    this.$['enroll-attributes-skip-button']
+        .addEventListener('tap', this.onSkipButtonClicked.bind(this));
+    this.$['enroll-attributes-submit-button']
+        .addEventListener('tap', this.onAttributesSubmitted.bind(this));
+
+
+    this.$['oauth-enroll-active-directory-join-error-card']
+        .addEventListener('buttonclick', function() {
+          this.showStep(ENROLLMENT_STEP.AD_JOIN);
+        }.bind(this));
+
+    this.navigation_.addEventListener('close', this.cancel.bind(this));
+    this.navigation_.addEventListener('refresh', this.cancel.bind(this));
+
+    this.$['oobe-signin-back-button']
+        .addEventListener('tap', this.onBackButtonClicked_.bind(this));
+
+
+    this.$['oauth-enroll-learn-more-link']
+        .addEventListener('click', function(event) {
+          chrome.send('oauthEnrollOnLearnMore');
+        });
+
+
+    this.licenseUi_.addEventListener('buttonclick', function() {
+      this.screen.onLicenseTypeSelected_(this.licenseUi_.selected);
+    }.bind(this));
+  },
+
+  /**
+   * Event handler that is invoked just before the frame is shown.
+   * @param {Object} data Screen init payload, contains the signin frame
+   * URL.
+   */
+  onBeforeShow: function(data) {
+    if (Oobe.getInstance().forceKeyboardFlow) {
+      // We run the tab remapping logic inside of the webview so that the
+      // simulated tab events will use the webview tab-stops. Simulated tab
+      // events created from the webui treat the entire webview as one tab
+      // stop. Real tab events do not do this. See crbug.com/543865.
+      this.$['oauth-enroll-auth-view'].addContentScripts([{
+        name: 'injectedTabHandler',
+        matches: ['http://*/*', 'https://*/*'],
+        js: {code: INJECTED_WEBVIEW_SCRIPT},
+        run_at: 'document_start'
+      }]);
+    }
+
+    this.$['oauth-enroll-auth-view'].partition = data.webviewPartitionName;
+
+    Oobe.getInstance().setSigninUIState(SIGNIN_UI_STATE.ENROLLMENT);
+    this.classList.remove('saml');
+
+    var gaiaParams = {};
+    gaiaParams.gaiaUrl = data.gaiaUrl;
+    gaiaParams.clientId = data.clientId;
+    gaiaParams.needPassword = false;
+    gaiaParams.hl = data.hl;
+    if (data.management_domain) {
+      gaiaParams.enterpriseEnrollmentDomain = data.management_domain;
+      gaiaParams.emailDomain = data.management_domain;
+    }
+    gaiaParams.flow = data.flow;
+    this.authenticator_.load(
+        cr.login.Authenticator.AuthMode.DEFAULT, gaiaParams);
+
+    var modes = ['manual', 'forced', 'recovery'];
+    for (var i = 0; i < modes.length; ++i) {
+      this.classList.toggle(
+          'mode-' + modes[i], data.enrollment_mode == modes[i]);
+    }
+    this.isManualEnrollment_ = data.enrollment_mode === 'manual';
+    this.navigation_.disabled = false;
+
+    this.offlineAdUi_.onBeforeShow();
+    if (!this.currentStep_) {
+      this.showStep(data.attestationBased ?
+          ENROLLMENT_STEP.WORKING : ENROLLMENT_STEP.SIGNIN);
+    }
+    this.behaviors.forEach((behavior) => {
+      if (behavior.onBeforeShow)
+        behavior.onBeforeShow.call(this);
+    });
+  },
+
+  onBeforeHide: function() {
+    Oobe.getInstance().setSigninUIState(SIGNIN_UI_STATE.HIDDEN);
+  },
+
+  /**
+   * Shows attribute-prompt step with pre-filled asset ID and
+   * location.
+   */
+  showAttributePromptStep: function(annotatedAssetId, annotatedLocation) {
+    this.$['oauth-enroll-asset-id'].value = annotatedAssetId;
+    this.$['oauth-enroll-location'].value = annotatedLocation;
+    this.showStep(ENROLLMENT_STEP.ATTRIBUTE_PROMPT);
+  },
+
+  /**
+   * Shows a success card for attestation-based enrollment that shows
+   * which domain the device was enrolled into.
+   */
+  showAttestationBasedEnrollmentSuccess: function(
+      device, enterpriseEnrollmentDomain) {
+    this.$['oauth-enroll-success-subtitle'].deviceName = device;
+    this.$['oauth-enroll-success-subtitle'].enrollmentDomain =
+        enterpriseEnrollmentDomain;
+    this.showStep(ENROLLMENT_STEP.SUCCESS);
+  },
+
+  /**
+   * Cancels the current authentication and drops the user back to the next
+   * screen (either the next authentication or the login screen).
+   */
+  cancel: function() {
+    if (this.isCancelDisabled)
+      return;
+    this.isCancelDisabled = true;
+    this.screen.closeEnrollment_('cancel');
+  },
+
+  /**
+   * Updates the list of available license types in license selection dialog.
+   */
+  setAvailableLicenseTypes: function(licenseTypes) {
+    var licenses = [
+      {
+        id: 'perpetual',
+        label: 'perpetualLicenseTypeTitle',
+      },
+      {
+        id: 'annual',
+        label: 'annualLicenseTypeTitle',
+      },
+      {
+        id: 'kiosk',
+        label: 'kioskLicenseTypeTitle',
+      }
+    ];
+    for (var i = 0, item; item = licenses[i]; ++i) {
+      if (item.id in licenseTypes) {
+        item.count = parseInt(licenseTypes[item.id]);
+        item.disabled = item.count == 0;
+        item.hidden = false;
+      } else {
+        item.count = 0;
+        item.disabled = true;
+        item.hidden = true;
+      }
+    }
+    this.licenseUi_.disabled = false;
+    this.licenseUi_.licenses = licenses;
+  },
+
+  /**
+   * Switches between the different steps in the enrollment flow.
+   * @param {string} step the steps to show, one of "signin", "working",
+   * "attribute-prompt", "error", "success".
+   */
+  showStep: function(step) {
+    let classList = this.$['oauth-enroll-step-contents'].classList;
+    classList.toggle('oauth-enroll-state-' + this.currentStep_, false);
+    classList.toggle('oauth-enroll-state-' + step, true);
+
+    this.isCancelDisabled =
+        (step == ENROLLMENT_STEP.SIGNIN && !this.isManualEnrollment_) ||
+        step == ENROLLMENT_STEP.AD_JOIN || step == ENROLLMENT_STEP.WORKING;
+    if (step == ENROLLMENT_STEP.SIGNIN) {
+      this.$['oauth-enroll-auth-view'].focus();
+    } else if (step == ENROLLMENT_STEP.LICENSE_TYPE) {
+      this.$['oauth-enroll-license-ui'].show();
+    } else if (step == ENROLLMENT_STEP.ERROR) {
+      this.$['oauth-enroll-error-card'].submitButton.focus();
+    } else if (step == ENROLLMENT_STEP.SUCCESS) {
+      this.$['oauth-enroll-success-card'].show();
+    } else if (step == ENROLLMENT_STEP.ATTRIBUTE_PROMPT) {
+      this.$['oauth-enroll-attribute-prompt-card'].show();
+    } else if (step == ENROLLMENT_STEP.ATTRIBUTE_PROMPT_ERROR) {
+      this.$['oauth-enroll-attribute-prompt-error-card'].submitButton.focus();
+    } else if (step == ENROLLMENT_STEP.ACTIVE_DIRECTORY_JOIN_ERROR) {
+      this.$['oauth-enroll-active-directory-join-error-card'].submitButton
+      .focus();
+    } else if (step == ENROLLMENT_STEP.AD_JOIN) {
+      this.offlineAdUi_.disabled = false;
+      this.offlineAdUi_.loading = false;
+      this.offlineAdUi_.focus();
+    }
+
+    this.currentStep_ = step;
+    this.lastBackMessageValue_ = false;
+    this.updateControlsState();
+  },
+
+  /**
+   * Sets an error message and switches to the error screen.
+   * @param {string} message the error message.
+   * @param {boolean} retry whether the retry link should be shown.
+   */
+  showError: function(message, retry) {
+    if (this.currentStep_ == ENROLLMENT_STEP.ATTRIBUTE_PROMPT) {
+      this.$['oauth-enroll-attribute-prompt-error-card'].textContent = message;
+      this.showStep(ENROLLMENT_STEP.ATTRIBUTE_PROMPT_ERROR);
+      return;
+    }
+    if (this.currentStep_ == ENROLLMENT_STEP.AD_JOIN) {
+      this.$['oauth-enroll-active-directory-join-error-card'].textContent =
+          message;
+      this.showStep(ENROLLMENT_STEP.ACTIVE_DIRECTORY_JOIN_ERROR);
+      return;
+    }
+    this.$['oauth-enroll-error-card'].textContent = message;
+    this.$['oauth-enroll-error-card'].buttonLabel =
+        retry ? loadTimeData.getString('oauthEnrollRetry') : '';
+    this.showStep(ENROLLMENT_STEP.ERROR);
+  },
+
+  doReload: function() {
+    this.lastBackMessageValue_ = false;
+    this.authenticator_.reload();
+    this.updateControlsState();
+  },
+
+  /**
+   * Sets Active Directory join screen params.
+   * @param {string} machineName
+   * @param {string} userName
+   * @param {ACTIVE_DIRECTORY_ERROR_STATE} errorState
+   * @param {boolean} showUnlockConfig true if there is an encrypted
+   * configuration (and not unlocked yet).
+   */
+  setAdJoinParams: function(
+      machineName, userName, errorState, showUnlockConfig) {
+    this.offlineAdUi_.disabled = false;
+    this.offlineAdUi_.machineName = machineName;
+    this.offlineAdUi_.userName = userName;
+    this.offlineAdUi_.errorState = errorState;
+    this.offlineAdUi_.unlockPasswordStep = showUnlockConfig;
+  },
+
+  /**
+   * Sets Active Directory join screen with the unlocked configuration.
+   * @param {Array<JoinConfigType>} options
+   */
+  setAdJoinConfiguration: function(options) {
+    this.offlineAdUi_.disabled = false;
+    this.offlineAdUi_.setJoinConfigurationOptions(options);
+    this.offlineAdUi_.unlockPasswordStep = false;
+  },
+
+  /**
+   * Retries the enrollment process after an error occurred in a previous
+   * attempt. This goes to the C++ side through |chrome| first to clean up the
+   * profile, so that the next attempt is performed with a clean state.
+   */
+  doRetry_: function() {
+    chrome.send('oauthEnrollRetry');
+  },
+
+  /**
+   * Skips the device attribute update,
+   * shows the successful enrollment step.
+   */
+  onSkipButtonClicked: function() {
+    this.showStep(ENROLLMENT_STEP.SUCCESS);
+  },
+
+  /**
+   * Skips the device attribute update,
+   * shows the successful enrollment step.
+   */
+  onBackButtonClicked_: function() {
+    if (this.currentStep_ == ENROLLMENT_STEP.SIGNIN) {
+      if (this.lastBackMessageValue_) {
+        this.lastBackMessageValue_ = false;
+        this.$['oauth-enroll-auth-view'].back();
+      } else {
+        this.cancel();
+      }
+    }
+  },
+
+
+  /**
+   * Uploads the device attributes to server. This goes to C++ side through
+   * |chrome| and launches the device attribute update negotiation.
+   */
+  onAttributesSubmitted: function() {
+    this.screen.onAttributesEntered_(this.$['oauth-enroll-asset-id'].value,
+        this.$['oauth-enroll-location'].value);
+  },
+
+  /**
+   * Returns true if we are at the begging of enrollment flow (i.e. the email
+   * page).
+   *
+   * @type {boolean}
+   */
+  isAtTheBeginning: function() {
+    return !this.lastBackMessageValue_ &&
+        this.currentStep_ == ENROLLMENT_STEP.SIGNIN;
+  },
+
+  /**
+   * Updates visibility of navigation buttons.
+   */
+  updateControlsState: function() {
+    this.navigation_.refreshVisible = this.isAtTheBeginning() &&
+        this.isManualEnrollment_ === false;
+    this.navigation_.closeVisible =
+        (this.currentStep_ == ENROLLMENT_STEP.ERROR
+            && !this.navigation_.refreshVisible) ||
+        this.currentStep_ == ENROLLMENT_STEP.LICENSE_TYPE;
+  },
+
+  /**
+   * Notifies chrome that enrollment have finished.
+   */
+  onEnrollmentFinished_: function() {
+    this.screen.closeEnrollment_('done');
+  },
+
+  updateLocalizedContent: function() {
+    this.offlineAdUi_.i18nUpdateLocale();
+  }
+});
diff --git a/chrome/browser/resources/chromeos/login/md_login.html b/chrome/browser/resources/chromeos/login/md_login.html
index 844aba8..7ba5cf9 100644
--- a/chrome/browser/resources/chromeos/login/md_login.html
+++ b/chrome/browser/resources/chromeos/login/md_login.html
@@ -57,7 +57,6 @@
 <link rel="stylesheet" href="oobe_screen_terms_of_service.css">
 <link rel="stylesheet" href="oobe_screen_auto_enrollment_check.css">
 <link rel="stylesheet" href="oobe_screen_supervision_transition.css">
-<link rel="stylesheet" href="oobe_screen_oauth_enrollment.css">
 <link rel="stylesheet" href="screen_app_launch_splash.css">
 <link rel="stylesheet" href="screen_arc_kiosk_splash.css">
 <link rel="stylesheet" href="screen_arc_terms_of_service.css">
diff --git a/chrome/browser/resources/chromeos/login/oobe.html b/chrome/browser/resources/chromeos/login/oobe.html
index 3ad80e1..48bfbaa 100644
--- a/chrome/browser/resources/chromeos/login/oobe.html
+++ b/chrome/browser/resources/chromeos/login/oobe.html
@@ -62,7 +62,6 @@
 <link rel="stylesheet" href="oobe_screen_terms_of_service.css">
 <link rel="stylesheet" href="oobe_screen_auto_enrollment_check.css">
 <link rel="stylesheet" href="oobe_screen_supervision_transition.css">
-<link rel="stylesheet" href="oobe_screen_oauth_enrollment.css">
 
 <link rel="stylesheet" href="screen_app_launch_splash.css">
 <link rel="stylesheet" href="screen_arc_kiosk_splash.css">
diff --git a/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.html b/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.html
index 7df1bcd..3d59747 100644
--- a/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.html
+++ b/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.html
@@ -5,126 +5,7 @@
 <link rel="import" href="chrome://oobe/custom_elements.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-iconset-svg/iron-iconset-svg.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="stylesheet" href="gaia_card_parameters.css">
 
 <div id="oauth-enrollment" class="step hidden" hidden>
-  <div id="oauth-enroll-step-contents">
-    <link rel="stylesheet" href="oobe_flex_layout.css">
-    <div id="oauth-enroll-step-signin">
-      <oobe-dialog class="gaia-dialog" role="dialog" id="enrollment-gaia-dialog"
-                   has-buttons no-header no-footer-padding>
-        <div slot="footer" class="flex layout vertical">
-          <webview id="oauth-enroll-auth-view" name="oauth-enroll-auth-view">
-          </webview>
-        </div>
-        <div slot="bottom-buttons"
-             class="flex layout horizontal center self-start">
-          <oobe-back-button id="oobe-signin-back-button"></oobe-back-button>
-        </div>
-      </oobe-dialog>
-    </div>
-    <div id="oauth-enroll-step-working">
-        <oobe-dialog id="oauth-enroll-working">
-          <hd-iron-icon slot="oobe-icon"
-                        icon1x="oobe-enrollment-32:briefcase"
-                        icon2x="oobe-enrollment-64:briefcase"></hd-iron-icon>
-          <h1 slot="title" i18n-content="oauthEnrollScreenTitle"></h1>
-          <paper-progress slot="progress" indeterminate>
-          </paper-progress>
-
-          <div slot="footer" class="flex layout vertical" role="alert">
-            <div class="oauth-enroll-step-message">
-              <span i18n-content="oauthEnrollWorking"></span>
-            </div>
-          </div>
-        </oobe-dialog>
-    </div>
-    <div id="oauth-enroll-step-license">
-      <enrollment-license-card id="oauth-enroll-license-ui"
-                               i18n-values="button-text:oauthEnrollNextBtn">
-      </enrollment-license-card>
-    </div>
-    <div id="oauth-enroll-step-ad-join">
-      <offline-ad-login id="oauth-enroll-ad-join-ui" is-domain-join
-          class="fit" i18n-values=
-              "ad-welcome-message:oauthEnrollAdDomainJoinWelcomeMessage">
-      </offline-ad-login>
-    </div>
-    <div id="oauth-enroll-step-error" role="alert">
-      <notification-card id="oauth-enroll-error-card" type="fail"
-          i18n-values="button-label:oauthEnrollRetry">
-      </notification-card>
-    </div>
-    <div id="oauth-enroll-step-success" role="alert">
-      <oobe-dialog id="oauth-enroll-success-card" has-buttons>
-        <hd-iron-icon slot="oobe-icon"
-                      icon1x="oobe-enrollment-32:briefcase"
-                      icon2x="oobe-enrollment-64:briefcase"></hd-iron-icon>
-        <h1 slot="title" i18n-content="oauthEnrollSuccessTitle"></h1>
-        <oobe-enrollment-success-with-domain id="oauth-enroll-success-subtitle"
-                                             slot="subtitle">
-        </oobe-enrollment-success-with-domain>
-        <div slot="footer" class="flex layout vertical center end-justified">
-          <img srcset="images/enrollment_success_illustration_1x.png 1x,
-                  images/enrollment_success_illustration_2x.png 2x"
-               i18n-values="alt:enrollmentSuccessIllustrationTitle">
-        </div>
-        <div slot="bottom-buttons" class="layout horizontal end-justified">
-          <oobe-text-button inverse id="enroll-success-done-button"
-              class="focus-on-show">
-            <div i18n-content="oauthEnrollDone"></div>
-          </oobe-text-button>
-        </div>
-      </oobe-dialog>
-    </div>
-    <div id="oauth-enroll-step-attribute-prompt">
-      <oobe-dialog id="oauth-enroll-attribute-prompt-card" has-buttons>
-        <hd-iron-icon slot="oobe-icon"
-                      icon1x="oobe-enrollment-32:briefcase"
-                      icon2x="oobe-enrollment-64:briefcase"></hd-iron-icon>
-        <h1 slot="title" i18n-content="oauthEnrollScreenTitle"></h1>
-        <div slot="subtitle" i18n-content="oauthEnrollDeviceInformation"></div>
-        <div slot="footer" class="layout vertical start">
-          <div class="oauth-enroll-step-message">
-                    <span id="oauth-enroll-attribute-prompt-message"
-                          i18n-content="oauthEnrollAttributeExplanation"></span>
-            <a href="#" id="oauth-enroll-learn-more-link"
-               class="oauth-enroll-link"
-               i18n-content="oauthEnrollExplainAttributeLink"></a>
-          </div>
-          <gaia-input id="oauth-enroll-asset-id" type="text"
-                      class="focus-on-show">
-            <div slot="label" i18n-content="oauthEnrollAssetIdLabel">
-            </div>
-          </gaia-input>
-          <gaia-input id="oauth-enroll-location" type="text">
-            <div slot="label" i18n-content="oauthEnrollLocationLabel">
-            </div>
-          </gaia-input>
-        </div>
-        <div slot="bottom-buttons" class="layout horizontal end-justified">
-          <oobe-text-button id="enroll-attributes-skip-button">
-            <div i18n-content="oauthEnrollSkip"></div>
-          </oobe-text-button>
-          <div class="flex"></div>
-          <oobe-next-button
-              id="enroll-attributes-submit-button"></oobe-next-button>
-        </div>
-      </oobe-dialog>
-    </div>
-    <div id="oauth-enroll-step-attribute-prompt-error">
-      <notification-card id="oauth-enroll-attribute-prompt-error-card"
-          type="fail" i18n-values="button-label:oauthEnrollDone">
-      </notification-card>
-    </div>
-    <div id="oauth-enroll-step-active-directory-join-error">
-      <notification-card id="oauth-enroll-active-directory-join-error-card"
-          type="fail" i18n-values="button-label:oauthEnrollRetry">
-      </notification-card>
-    </div>
-  </div>
-  <div id="oauth-saml-notice-container">
-    <span id="oauth-saml-notice-message"></span>
-  </div>
-  <navigation-bar id="oauth-enroll-navigation"></navigation-bar>
+  <enterprise-enrollment id="enterprise-enrollment"></enterprise-enrollment>
 </div>
diff --git a/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.js b/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.js
index 868ec34..c0c9e4e 100644
--- a/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.js
+++ b/chrome/browser/resources/chromeos/login/oobe_screen_oauth_enrollment.js
@@ -3,26 +3,6 @@
 // found in the LICENSE file.
 
 login.createScreen('OAuthEnrollmentScreen', 'oauth-enrollment', function() {
-  /* Code which is embedded inside of the webview. See below for details.
-  /** @const */ var INJECTED_WEBVIEW_SCRIPT = String.raw`
-                      (function() {
-                         // <include src="../keyboard/keyboard_utils.js">
-                         keyboard.initializeKeyboardFlow(true);
-                       })();`;
-
-  /** @const */ var STEP_SIGNIN = 'signin';
-  /** @const */ var STEP_AD_JOIN = 'ad-join';
-  /** @const */ var STEP_LICENSE_TYPE = 'license';
-  /** @const */ var STEP_WORKING = 'working';
-  /** @const */ var STEP_ATTRIBUTE_PROMPT = 'attribute-prompt';
-  /** @const */ var STEP_ERROR = 'error';
-  /** @const */ var STEP_SUCCESS = 'success';
-
-  /* TODO(dzhioev): define this step on C++ side.
-  /** @const */ var STEP_ATTRIBUTE_PROMPT_ERROR = 'attribute-prompt-error';
-  /** @const */ var STEP_ACTIVE_DIRECTORY_JOIN_ERROR =
-      'active-directory-join-error';
-
   return {
     EXTERNAL_API: [
       'showStep',
@@ -36,257 +16,23 @@
     ],
 
     /**
-     * Authenticator object that wraps GAIA webview.
+     * Returns the control which should receive initial focus.
      */
-    authenticator_: null,
-
-    /**
-     * The current step. This is the last value passed to showStep().
-     */
-    currentStep_: null,
-
-    /**
-     * We block esc, back button and cancel button until gaia is loaded to
-     * prevent multiple cancel events.
-     */
-    isCancelDisabled_: null,
-
-    get isCancelDisabled() {
-      return this.isCancelDisabled_;
-    },
-    set isCancelDisabled(disabled) {
-      this.isCancelDisabled_ = disabled;
+    get defaultControl() {
+      return $('enterprise-enrollment');
     },
 
-    isManualEnrollment_: undefined,
-
     /**
-     * An element containing UI for picking license type.
-     * @type {EnrollmentLicenseCard}
-     * @private
+     * This is called after resources are updated.
      */
-    licenseUi_: undefined,
+    updateLocalizedContent: function() {
+      $('enterprise-enrollment').updateLocalizedContent();
+    },
 
-    /**
-     * An element containg navigation buttons.
-     */
-    navigation_: undefined,
-
-    /**
-     * An element containing UI to join an AD domain.
-     * @type {OfflineAdLoginElement}
-     * @private
-     */
-    offlineAdUi_: undefined,
-
-    /**
-     * Value contained in the last received 'backButton' event.
-     * @type {boolean}
-     * @private
-     */
-    lastBackMessageValue_: false,
 
     /** @override */
     decorate: function() {
-      this.navigation_ = $('oauth-enroll-navigation');
-      this.offlineAdUi_ = $('oauth-enroll-ad-join-ui');
-      this.licenseUi_ = $('oauth-enroll-license-ui');
-
-      this.authenticator_ =
-          new cr.login.Authenticator($('oauth-enroll-auth-view'));
-
-      // Establish an initial messaging between content script and
-      // host script so that content script can message back.
-      $('oauth-enroll-auth-view').addEventListener('loadstop', function(e) {
-        e.target.contentWindow.postMessage(
-            'initialMessage', $('oauth-enroll-auth-view').src);
-      });
-
-      // When we get the advancing focus command message from injected content
-      // script, we can execute it on host script context.
-      window.addEventListener('message', function(e) {
-        if (e.data == 'forwardFocus')
-          keyboard.onAdvanceFocus(false);
-        else if (e.data == 'backwardFocus')
-          keyboard.onAdvanceFocus(true);
-      });
-
-      this.authenticator_.addEventListener(
-          'ready', (function() {
-                     if (this.currentStep_ != STEP_SIGNIN)
-                       return;
-                     this.isCancelDisabled = false;
-                     chrome.send('frameLoadingCompleted');
-                   }).bind(this));
-
-      this.authenticator_.addEventListener(
-          'authCompleted',
-          (function(e) {
-            var detail = e.detail;
-            if (!detail.email) {
-              this.showError(
-                  loadTimeData.getString('fatalEnrollmentError'), false);
-              return;
-            }
-            chrome.send('oauthEnrollCompleteLogin', [detail.email]);
-          }).bind(this));
-
-      this.offlineAdUi_.addEventListener('authCompleted', function(e) {
-        this.offlineAdUi_.disabled = true;
-        this.offlineAdUi_.loading = true;
-        chrome.send('oauthEnrollAdCompleteLogin', [
-          e.detail.machine_name, e.detail.distinguished_name,
-          e.detail.encryption_types, e.detail.username, e.detail.password
-        ]);
-      }.bind(this));
-      this.offlineAdUi_.addEventListener('unlockPasswordEntered', function(e) {
-        this.offlineAdUi_.disabled = true;
-        chrome.send(
-            'oauthEnrollAdUnlockConfiguration', [e.detail.unlock_password]);
-      }.bind(this));
-
-      this.authenticator_.addEventListener(
-          'authFlowChange', (function(e) {
-                              var isSAML = this.authenticator_.authFlow ==
-                                  cr.login.Authenticator.AuthFlow.SAML;
-                              if (isSAML) {
-                                $('oauth-saml-notice-message').textContent =
-                                    loadTimeData.getStringF(
-                                        'samlNotice',
-                                        this.authenticator_.authDomain);
-                              }
-                              this.classList.toggle('saml', isSAML);
-                              if (Oobe.getInstance().currentScreen == this)
-                                Oobe.getInstance().updateScreenSize(this);
-                              this.lastBackMessageValue_ = false;
-                              this.updateControlsState();
-                            }).bind(this));
-
-      this.authenticator_.addEventListener(
-          'backButton', (function(e) {
-                          this.lastBackMessageValue_ = !!e.detail;
-                          $('oauth-enroll-auth-view').focus();
-                          this.updateControlsState();
-                        }).bind(this));
-
-      this.authenticator_.addEventListener(
-          'dialogShown', (function(e) {
-                           this.navigation_.disabled = true;
-                           $('oobe-signin-back-button').disabled = true;
-                           // TODO(alemate): update the visual style.
-                         }).bind(this));
-
-      this.authenticator_.addEventListener(
-          'dialogHidden', (function(e) {
-                            this.navigation_.disabled = false;
-                            $('oobe-signin-back-button').disabled = false;
-                            // TODO(alemate): update the visual style.
-                          }).bind(this));
-
-      this.authenticator_.insecureContentBlockedCallback =
-          (function(url) {
-            this.showError(
-                loadTimeData.getStringF('insecureURLEnrollmentError', url),
-                false);
-          }).bind(this);
-
-      this.authenticator_.missingGaiaInfoCallback =
-          (function() {
-            this.showError(
-                loadTimeData.getString('fatalEnrollmentError'), false);
-          }).bind(this);
-
-      $('oauth-enroll-error-card')
-          .addEventListener('buttonclick', this.doRetry_.bind(this));
-
-      $('oauth-enroll-attribute-prompt-error-card')
-          .addEventListener(
-              'buttonclick', this.onEnrollmentFinished_.bind(this));
-
-      $('enroll-success-done-button')
-          .addEventListener('tap', this.onEnrollmentFinished_.bind(this));
-
-      $('enroll-attributes-skip-button')
-          .addEventListener('tap', this.onSkipButtonClicked.bind(this));
-      $('enroll-attributes-submit-button')
-          .addEventListener('tap', this.onAttributesSubmitted.bind(this));
-
-
-      $('oauth-enroll-active-directory-join-error-card')
-          .addEventListener('buttonclick', function() {
-            this.showStep(STEP_AD_JOIN);
-          }.bind(this));
-
-      this.navigation_.addEventListener('close', this.cancel.bind(this));
-      this.navigation_.addEventListener('refresh', this.cancel.bind(this));
-
-      $('oobe-signin-back-button')
-          .addEventListener('tap', this.onBackButtonClicked_.bind(this));
-
-
-      $('oauth-enroll-learn-more-link')
-          .addEventListener('click', function(event) {
-            chrome.send('oauthEnrollOnLearnMore');
-          });
-
-
-      this.licenseUi_.addEventListener('buttonclick', function() {
-        chrome.send('onLicenseTypeSelected', [this.licenseUi_.selected]);
-      }.bind(this));
-    },
-
-    /**
-     * Event handler that is invoked just before the frame is shown.
-     * @param {Object} data Screen init payload, contains the signin frame
-     * URL.
-     */
-    onBeforeShow: function(data) {
-      if (Oobe.getInstance().forceKeyboardFlow) {
-        // We run the tab remapping logic inside of the webview so that the
-        // simulated tab events will use the webview tab-stops. Simulated tab
-        // events created from the webui treat the entire webview as one tab
-        // stop. Real tab events do not do this. See crbug.com/543865.
-        $('oauth-enroll-auth-view').addContentScripts([{
-          name: 'injectedTabHandler',
-          matches: ['http://*/*', 'https://*/*'],
-          js: {code: INJECTED_WEBVIEW_SCRIPT},
-          run_at: 'document_start'
-        }]);
-      }
-
-      $('oauth-enroll-auth-view').partition = data.webviewPartitionName;
-
-      Oobe.getInstance().setSigninUIState(SIGNIN_UI_STATE.ENROLLMENT);
-      this.classList.remove('saml');
-
-      var gaiaParams = {};
-      gaiaParams.gaiaUrl = data.gaiaUrl;
-      gaiaParams.clientId = data.clientId;
-      gaiaParams.needPassword = false;
-      gaiaParams.hl = data.hl;
-      if (data.management_domain) {
-        gaiaParams.enterpriseEnrollmentDomain = data.management_domain;
-        gaiaParams.emailDomain = data.management_domain;
-      }
-      gaiaParams.flow = data.flow;
-      this.authenticator_.load(
-          cr.login.Authenticator.AuthMode.DEFAULT, gaiaParams);
-
-      var modes = ['manual', 'forced', 'recovery'];
-      for (var i = 0; i < modes.length; ++i) {
-        this.classList.toggle(
-            'mode-' + modes[i], data.enrollment_mode == modes[i]);
-      }
-      this.isManualEnrollment_ = data.enrollment_mode === 'manual';
-      this.navigation_.disabled = false;
-
-      this.offlineAdUi_.onBeforeShow();
-      if (!this.currentStep_)
-        this.showStep(data.attestationBased ? STEP_WORKING : STEP_SIGNIN);
-    },
-
-    onBeforeHide: function() {
-      Oobe.getInstance().setSigninUIState(SIGNIN_UI_STATE.HIDDEN);
+      $('enterprise-enrollment').screen = this;
     },
 
     /**
@@ -294,9 +40,8 @@
      * location.
      */
     showAttributePromptStep: function(annotatedAssetId, annotatedLocation) {
-      $('oauth-enroll-asset-id').value = annotatedAssetId;
-      $('oauth-enroll-location').value = annotatedLocation;
-      this.showStep(STEP_ATTRIBUTE_PROMPT);
+      $('enterprise-enrollment')
+          .showAttributePromptStep(annotatedAssetId, annotatedLocation);
     },
 
     /**
@@ -305,54 +50,16 @@
      */
     showAttestationBasedEnrollmentSuccess: function(
         device, enterpriseEnrollmentDomain) {
-      $('oauth-enroll-success-subtitle').deviceName = device;
-      $('oauth-enroll-success-subtitle').enrollmentDomain =
-          enterpriseEnrollmentDomain;
-      this.showStep(STEP_SUCCESS);
-    },
-
-    /**
-     * Cancels the current authentication and drops the user back to the next
-     * screen (either the next authentication or the login screen).
-     */
-    cancel: function() {
-      if (this.isCancelDisabled)
-        return;
-      this.isCancelDisabled = true;
-      chrome.send('oauthEnrollClose', ['cancel']);
+      $('enterprise-enrollment')
+          .showAttestationBasedEnrollmentSuccess(
+              device, enterpriseEnrollmentDomain);
     },
 
     /**
      * Updates the list of available license types in license selection dialog.
      */
     setAvailableLicenseTypes: function(licenseTypes) {
-      var licenses = [
-        {
-          id: 'perpetual',
-          label: 'perpetualLicenseTypeTitle',
-        },
-        {
-          id: 'annual',
-          label: 'annualLicenseTypeTitle',
-        },
-        {
-          id: 'kiosk',
-          label: 'kioskLicenseTypeTitle',
-        }
-      ];
-      for (var i = 0, item; item = licenses[i]; ++i) {
-        if (item.id in licenseTypes) {
-          item.count = parseInt(licenseTypes[item.id]);
-          item.disabled = item.count == 0;
-          item.hidden = false;
-        } else {
-          item.count = 0;
-          item.disabled = true;
-          item.hidden = true;
-        }
-      }
-      this.licenseUi_.disabled = false;
-      this.licenseUi_.licenses = licenses;
+      $('enterprise-enrollment').setAvailableLicenseTypes(licenseTypes);
     },
 
     /**
@@ -361,35 +68,7 @@
      * "attribute-prompt", "error", "success".
      */
     showStep: function(step) {
-      this.classList.toggle('oauth-enroll-state-' + this.currentStep_, false);
-      this.classList.toggle('oauth-enroll-state-' + step, true);
-
-      this.isCancelDisabled =
-          (step == STEP_SIGNIN && !this.isManualEnrollment_) ||
-          step == STEP_AD_JOIN || step == STEP_WORKING;
-      if (step == STEP_SIGNIN) {
-        $('oauth-enroll-auth-view').focus();
-      } else if (step == STEP_LICENSE_TYPE) {
-        $('oauth-enroll-license-ui').show();
-      } else if (step == STEP_ERROR) {
-        $('oauth-enroll-error-card').submitButton.focus();
-      } else if (step == STEP_SUCCESS) {
-        $('oauth-enroll-success-card').show();
-      } else if (step == STEP_ATTRIBUTE_PROMPT) {
-        $('oauth-enroll-attribute-prompt-card').show();
-      } else if (step == STEP_ATTRIBUTE_PROMPT_ERROR) {
-        $('oauth-enroll-attribute-prompt-error-card').submitButton.focus();
-      } else if (step == STEP_ACTIVE_DIRECTORY_JOIN_ERROR) {
-        $('oauth-enroll-active-directory-join-error-card').submitButton.focus();
-      } else if (step == STEP_AD_JOIN) {
-        this.offlineAdUi_.disabled = false;
-        this.offlineAdUi_.loading = false;
-        this.offlineAdUi_.focus();
-      }
-
-      this.currentStep_ = step;
-      this.lastBackMessageValue_ = false;
-      this.updateControlsState();
+      $('enterprise-enrollment').showStep(step);
     },
 
     /**
@@ -398,27 +77,11 @@
      * @param {boolean} retry whether the retry link should be shown.
      */
     showError: function(message, retry) {
-      if (this.currentStep_ == STEP_ATTRIBUTE_PROMPT) {
-        $('oauth-enroll-attribute-prompt-error-card').textContent = message;
-        this.showStep(STEP_ATTRIBUTE_PROMPT_ERROR);
-        return;
-      }
-      if (this.currentStep_ == STEP_AD_JOIN) {
-        $('oauth-enroll-active-directory-join-error-card').textContent =
-            message;
-        this.showStep(STEP_ACTIVE_DIRECTORY_JOIN_ERROR);
-        return;
-      }
-      $('oauth-enroll-error-card').textContent = message;
-      $('oauth-enroll-error-card').buttonLabel =
-          retry ? loadTimeData.getString('oauthEnrollRetry') : '';
-      this.showStep(STEP_ERROR);
+      $('enterprise-enrollment').showError(message, retry);
     },
 
     doReload: function() {
-      this.lastBackMessageValue_ = false;
-      this.authenticator_.reload();
-      this.updateControlsState();
+      $('enterprise-enrollment').doReload();
     },
 
     /**
@@ -431,11 +94,8 @@
      */
     setAdJoinParams: function(
         machineName, userName, errorState, showUnlockConfig) {
-      this.offlineAdUi_.disabled = false;
-      this.offlineAdUi_.machineName = machineName;
-      this.offlineAdUi_.userName = userName;
-      this.offlineAdUi_.errorState = errorState;
-      this.offlineAdUi_.unlockPasswordStep = showUnlockConfig;
+      $('enterprise-enrollment')
+          .setAdJoinParams(machineName, userName, errorState, showUnlockConfig);
     },
 
     /**
@@ -443,84 +103,39 @@
      * @param {Array<JoinConfigType>} options
      */
     setAdJoinConfiguration: function(options) {
-      this.offlineAdUi_.disabled = false;
-      this.offlineAdUi_.setJoinConfigurationOptions(options);
-      this.offlineAdUi_.unlockPasswordStep = false;
+      $('enterprise-enrollment').setAdJoinConfiguration(options);
     },
 
-    /**
-     * Retries the enrollment process after an error occurred in a previous
-     * attempt. This goes to the C++ side through |chrome| first to clean up the
-     * profile, so that the next attempt is performed with a clean state.
-     */
-    doRetry_: function() {
-      chrome.send('oauthEnrollRetry');
+    closeEnrollment_: function(result) {
+      chrome.send('oauthEnrollClose', [result]);
     },
 
-    /**
-     * Skips the device attribute update,
-     * shows the successful enrollment step.
-     */
-    onSkipButtonClicked: function() {
-      this.showStep(STEP_SUCCESS);
+    onAttributesEntered_: function(asset_id, location) {
+      chrome.send('oauthEnrollAttributes', [asset_id, location]);
     },
 
-    /**
-     * Skips the device attribute update,
-     * shows the successful enrollment step.
-     */
-    onBackButtonClicked_: function() {
-      if (this.currentStep_ == STEP_SIGNIN) {
-        if (this.lastBackMessageValue_) {
-          this.lastBackMessageValue_ = false;
-          $('oauth-enroll-auth-view').back();
-        } else {
-          this.cancel();
-        }
-      }
+    onAuthCompleted_: function(email) {
+      chrome.send('oauthEnrollCompleteLogin', [email]);
     },
 
-
-    /**
-     * Uploads the device attributes to server. This goes to C++ side through
-     * |chrome| and launches the device attribute update negotiation.
-     */
-    onAttributesSubmitted: function() {
-      chrome.send(
-          'oauthEnrollAttributes',
-          [$('oauth-enroll-asset-id').value, $('oauth-enroll-location').value]);
+    onAuthFrameLoaded_: function() {
+      chrome.send('frameLoadingCompleted');
     },
 
-    /**
-     * Returns true if we are at the begging of enrollment flow (i.e. the email
-     * page).
-     *
-     * @type {boolean}
-     */
-    isAtTheBeginning: function() {
-      return !this.lastBackMessageValue_ && this.currentStep_ == STEP_SIGNIN;
+    onAdCompleteLogin_: function(
+        machine_name, distinguished_name, encryption_types, username,
+        password) {
+      chrome.send('oauthEnrollAdCompleteLogin', [
+        machine_name, distinguished_name, encryption_types, username, password
+      ]);
     },
 
-    /**
-     * Updates visibility of navigation buttons.
-     */
-    updateControlsState: function() {
-      this.navigation_.refreshVisible =
-          this.isAtTheBeginning() && this.isManualEnrollment_ === false;
-      this.navigation_.closeVisible =
-          (this.currentStep_ == STEP_ERROR && !this.navigation_.refreshVisible)
-          || this.currentStep_ == STEP_LICENSE_TYPE;
+    onAdUnlockConfiguration_: function(unlock_password) {
+      chrome.send('oauthEnrollAdUnlockConfiguration', [unlock_password]);
     },
 
-    /**
-     * Notifies chrome that enrollment have finished.
-     */
-    onEnrollmentFinished_: function() {
-      chrome.send('oauthEnrollClose', ['done']);
-    },
-
-    updateLocalizedContent: function() {
-      this.offlineAdUi_.i18nUpdateLocale();
+    onLicenseTypeSelected_: function(license_type) {
+      chrome.send('onLicenseTypeSelected', [license_type]);
     },
   };
 });
diff --git a/chrome/browser/search/instant_service.cc b/chrome/browser/search/instant_service.cc
index 512167f..059578d 100644
--- a/chrome/browser/search/instant_service.cc
+++ b/chrome/browser/search/instant_service.cc
@@ -584,20 +584,6 @@
          !pref_service_->GetBoolean(prefs::kNtpUseMostVisitedTiles);
 }
 
-namespace {
-
-// Converts SkColor to RGBAColor
-RGBAColor SkColorToRGBAColor(const SkColor& sKColor) {
-  RGBAColor color;
-  color.r = SkColorGetR(sKColor);
-  color.g = SkColorGetG(sKColor);
-  color.b = SkColorGetB(sKColor);
-  color.a = SkColorGetA(sKColor);
-  return color;
-}
-
-}  // namespace
-
 void InstantService::BuildThemeInfo() {
   // Get theme information from theme service.
   theme_info_.reset(new ThemeBackgroundInfo());
@@ -618,9 +604,9 @@
       theme_provider.GetColor(ThemeProperties::COLOR_NTP_TEXT_LIGHT);
 
   // Set colors.
-  theme_info_->background_color = SkColorToRGBAColor(background_color);
-  theme_info_->text_color = SkColorToRGBAColor(text_color);
-  theme_info_->text_color_light = SkColorToRGBAColor(text_color_light);
+  theme_info_->background_color = background_color;
+  theme_info_->text_color = text_color;
+  theme_info_->text_color_light = text_color_light;
 
   int logo_alternate =
       theme_provider.GetDisplayProperty(ThemeProperties::NTP_LOGO_ALTERNATE);
diff --git a/chrome/browser/sharing/sharing_sync_preference.cc b/chrome/browser/sharing/sharing_sync_preference.cc
new file mode 100644
index 0000000..88a4cb22
--- /dev/null
+++ b/chrome/browser/sharing/sharing_sync_preference.cc
@@ -0,0 +1,135 @@
+// 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/sharing/sharing_sync_preference.h"
+
+#include "base/base64.h"
+#include "base/value_conversions.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/sync_preferences/pref_service_syncable.h"
+
+namespace {
+
+const char kVapidKey[] = "sharing.vapid_key";
+const char kVapidECPrivateKey[] = "vapid_private_key";
+const char kVapidCreationTimestamp[] = "vapid_creation_timestamp";
+
+const char kSyncedDevices[] = "sharing.synced_devices";
+const char kDeviceFcmToken[] = "device_fcm_token";
+const char kDeviceP256dh[] = "device_p256dh";
+const char kDeviceAuthSecret[] = "device_auth_secret";
+const char kDeviceCapabilities[] = "device_capabilities";
+}  // namespace
+
+SharingSyncPreference::Device::Device(const std::string& fcm_token,
+                                      const std::string& p256dh,
+                                      const std::string& auth_secret,
+                                      const int capabilities)
+    : fcm_token(fcm_token),
+      p256dh(p256dh),
+      auth_secret(auth_secret),
+      capabilities(capabilities) {}
+
+SharingSyncPreference::Device::Device(Device&& other) = default;
+
+SharingSyncPreference::Device::~Device() = default;
+
+SharingSyncPreference::SharingSyncPreference(PrefService* prefs)
+    : prefs_(prefs) {}
+
+SharingSyncPreference::~SharingSyncPreference() = default;
+
+// static
+void SharingSyncPreference::RegisterProfilePrefs(
+    user_prefs::PrefRegistrySyncable* registry) {
+  registry->RegisterDictionaryPref(
+      kSyncedDevices, user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+  registry->RegisterDictionaryPref(
+      kVapidKey, user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+}
+
+base::Optional<std::vector<uint8_t>> SharingSyncPreference::GetVapidKey()
+    const {
+  const base::DictionaryValue* vapid_key = prefs_->GetDictionary(kVapidKey);
+  std::string base64_private_key, private_key;
+  if (!vapid_key->GetString(kVapidECPrivateKey, &base64_private_key))
+    return base::nullopt;
+
+  if (base::Base64Decode(base64_private_key, &private_key)) {
+    return std::vector<uint8_t>(private_key.begin(), private_key.end());
+  } else {
+    LOG(ERROR) << "Could not decode stored vapid keys.";
+    return base::nullopt;
+  }
+}
+
+void SharingSyncPreference::SetVapidKey(
+    const std::vector<uint8_t>& vapid_key) const {
+  base::Time creation_timestamp = base::Time::Now();
+  std::string base64_vapid_key;
+  base::Base64Encode(std::string(vapid_key.begin(), vapid_key.end()),
+                     &base64_vapid_key);
+
+  DictionaryPrefUpdate update(prefs_, kVapidKey);
+  update->SetString(kVapidECPrivateKey, base64_vapid_key);
+  update->SetString(kVapidCreationTimestamp,
+                    base::CreateTimeValue(creation_timestamp).GetString());
+}
+
+std::map<std::string, SharingSyncPreference::Device>
+SharingSyncPreference::GetSyncedDevices() const {
+  std::map<std::string, Device> synced_devices;
+  const base::DictionaryValue* devices_preferences =
+      prefs_->GetDictionary(kSyncedDevices);
+  for (const auto& it : devices_preferences->DictItems()) {
+    base::Optional<Device> device = ValueToDevice(it.second);
+    if (device)
+      synced_devices.emplace(it.first, std::move(*device));
+  }
+  return synced_devices;
+}
+
+void SharingSyncPreference::SetSyncDevice(const std::string& guid,
+                                          const Device& device) {
+  DictionaryPrefUpdate update(prefs_, kSyncedDevices);
+  update->SetKey(guid, DeviceToValue(device));
+}
+
+void SharingSyncPreference::RemoveDevice(const std::string& guid) {
+  DictionaryPrefUpdate update(prefs_, kSyncedDevices);
+  update->RemoveKey(guid);
+}
+
+base::Value SharingSyncPreference::DeviceToValue(const Device& device) const {
+  std::string base64_p256dh, base64_auth_secret;
+  base::Base64Encode(device.p256dh, &base64_p256dh);
+  base::Base64Encode(device.auth_secret, &base64_auth_secret);
+
+  base::Value result(base::Value::Type::DICTIONARY);
+  result.SetStringKey(kDeviceFcmToken, device.fcm_token);
+  result.SetStringKey(kDeviceP256dh, base64_p256dh);
+  result.SetStringKey(kDeviceAuthSecret, base64_auth_secret);
+  result.SetIntKey(kDeviceCapabilities, device.capabilities);
+  return result;
+}
+
+base::Optional<SharingSyncPreference::Device>
+SharingSyncPreference::ValueToDevice(const base::Value& value) const {
+  const std::string* fcm_token = value.FindStringKey(kDeviceFcmToken);
+  const std::string* base64_p256dh = value.FindStringKey(kDeviceP256dh);
+  const std::string* base64_auth_secret =
+      value.FindStringKey(kDeviceAuthSecret);
+  const base::Optional<int> capabilities =
+      value.FindIntKey(kDeviceCapabilities);
+
+  std::string p256dh, auth_secret;
+  if (!fcm_token || !base64_p256dh || !base64_auth_secret || !capabilities ||
+      !base::Base64Decode(*base64_p256dh, &p256dh) ||
+      !base::Base64Decode(*base64_auth_secret, &auth_secret)) {
+    LOG(ERROR) << "Could not convert synced value to device object.";
+    return base::nullopt;
+  } else {
+    return Device(*fcm_token, p256dh, auth_secret, *capabilities);
+  }
+}
diff --git a/chrome/browser/sharing/sharing_sync_preference.h b/chrome/browser/sharing/sharing_sync_preference.h
new file mode 100644
index 0000000..050ace6
--- /dev/null
+++ b/chrome/browser/sharing/sharing_sync_preference.h
@@ -0,0 +1,86 @@
+// 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_SHARING_SHARING_SYNC_PREFERENCE_H_
+#define CHROME_BROWSER_SHARING_SHARING_SYNC_PREFERENCE_H_
+
+#include <stdint.h>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/values.h"
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
+class PrefService;
+
+// SharingSyncPreference manages all preferences related to Sharing using Sync,
+// such as storing list of user devices synced via Chrome and VapidKey used
+// for authentication.
+class SharingSyncPreference {
+ public:
+  struct Device {
+    Device(const std::string& fcm_token,
+           const std::string& p256dh,
+           const std::string& auth_secret,
+           const int capabilities);
+    Device(Device&& other);
+    ~Device();
+
+    // FCM registration token of device for sending SharingMessage.
+    std::string fcm_token;
+
+    // Subscription public key required for WebPush protocol.
+    std::string p256dh;
+
+    // Auth secret key required for WebPush protocol.
+    std::string auth_secret;
+
+    // Bitmask of capabilities, defined in SharingDeviceCapability enum, that
+    // are supported by the device.
+    int capabilities;
+  };
+
+  explicit SharingSyncPreference(PrefService* prefs);
+  ~SharingSyncPreference();
+
+  static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+  // Returns VAPID key from preferences if present, otherwise returns
+  // base::nullopt.
+  // For more information on vapid keys, please see
+  // https://tools.ietf.org/html/draft-thomson-webpush-vapid-02
+  base::Optional<std::vector<uint8_t>> GetVapidKey() const;
+
+  // Adds VAPID key to preferences for syncing across devices.
+  void SetVapidKey(const std::vector<uint8_t>& vapid_key) const;
+
+  // Returns the map of guid to device from sharing preferences. Guid is same
+  // as sync device guid.
+  std::map<std::string, Device> GetSyncedDevices() const;
+
+  // Stores |device| with key |guid| in sharing preferences.
+  // |guid| is same as sync device guid.
+  void SetSyncDevice(const std::string& guid, const Device& device);
+
+  // Removes device corresponding to |guid| from sharing preferences.
+  // |guid| is same as sync device guid.
+  void RemoveDevice(const std::string& guid);
+
+ private:
+  base::Value DeviceToValue(const Device& device) const;
+
+  base::Optional<Device> ValueToDevice(const base::Value& value) const;
+
+  PrefService* prefs_;
+
+  DISALLOW_COPY_AND_ASSIGN(SharingSyncPreference);
+};
+
+#endif  // CHROME_BROWSER_SHARING_SHARING_SYNC_PREFERENCE_H_
diff --git a/chrome/browser/sharing/sharing_sync_preference_unittest.cc b/chrome/browser/sharing/sharing_sync_preference_unittest.cc
new file mode 100644
index 0000000..af638ae
--- /dev/null
+++ b/chrome/browser/sharing/sharing_sync_preference_unittest.cc
@@ -0,0 +1,78 @@
+// 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/sharing/sharing_sync_preference.h"
+
+#include <memory>
+
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char kVapidKeyStr[] = "test_vapid_key";
+const std::vector<uint8_t> kVapidKey =
+    std::vector<uint8_t>(std::begin(kVapidKeyStr), std::end(kVapidKeyStr));
+
+const char kDeviceGuid[] = "test_device";
+const char kDeviceFcmToken[] = "test_fcm_token";
+const char kDeviceAuthToken[] = "test_auth_token";
+const char kDeviceP256dh[] = "test_p256dh";
+const int kCapabilities = 1;
+
+class SharingSyncPreferenceTest : public testing::Test {
+ protected:
+  SharingSyncPreferenceTest() : sharing_sync_preference_(&prefs_) {
+    SharingSyncPreference::RegisterProfilePrefs(prefs_.registry());
+  }
+
+  void SyncDefaultDevice() {
+    sharing_sync_preference_.SetSyncDevice(
+        kDeviceGuid,
+        SharingSyncPreference::Device(kDeviceFcmToken, kDeviceP256dh,
+                                      kDeviceAuthToken, kCapabilities));
+  }
+
+  base::Optional<SharingSyncPreference::Device> GetDevice(
+      const std::string& guid) {
+    std::map<std::string, SharingSyncPreference::Device> synced_devices =
+        sharing_sync_preference_.GetSyncedDevices();
+    auto it = synced_devices.find(guid);
+    if (it == synced_devices.end())
+      return base::nullopt;
+    else
+      return std::move(it->second);
+  }
+
+  sync_preferences::TestingPrefServiceSyncable prefs_;
+  SharingSyncPreference sharing_sync_preference_;
+};
+
+}  // namespace
+
+TEST_F(SharingSyncPreferenceTest, UpdateVapidKeys) {
+  EXPECT_EQ(base::nullopt, sharing_sync_preference_.GetVapidKey());
+  sharing_sync_preference_.SetVapidKey(kVapidKey);
+  EXPECT_EQ(kVapidKey, sharing_sync_preference_.GetVapidKey());
+}
+
+TEST_F(SharingSyncPreferenceTest, RemoveDevice) {
+  SyncDefaultDevice();
+  EXPECT_NE(base::nullopt, GetDevice(kDeviceGuid));
+  sharing_sync_preference_.RemoveDevice(kDeviceGuid);
+  EXPECT_EQ(base::nullopt, GetDevice(kDeviceGuid));
+}
+
+TEST_F(SharingSyncPreferenceTest, SyncDevice) {
+  EXPECT_EQ(base::nullopt, GetDevice(kDeviceGuid));
+  SyncDefaultDevice();
+  base::Optional<SharingSyncPreference::Device> device = GetDevice(kDeviceGuid);
+
+  EXPECT_NE(base::nullopt, device);
+  EXPECT_EQ(kDeviceFcmToken, device->fcm_token);
+  EXPECT_EQ(kDeviceP256dh, device->p256dh);
+  EXPECT_EQ(kDeviceAuthToken, device->auth_secret);
+  EXPECT_EQ(kCapabilities, device->capabilities);
+}
diff --git a/chrome/browser/sync/test/integration/single_client_app_list_sync_test.cc b/chrome/browser/sync/test/integration/single_client_app_list_sync_test.cc
index 1953e6a..a0876eb 100644
--- a/chrome/browser/sync/test/integration/single_client_app_list_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_app_list_sync_test.cc
@@ -26,6 +26,16 @@
   return SyncAppListHelper::GetInstance()->AllProfilesHaveSameAppList();
 }
 
+// Returns true if sync items from |service| all have non-empty names.
+bool SyncItemsHaveNames(const app_list::AppListSyncableService* service) {
+  for (const auto& it : service->sync_items()) {
+    if (it.second->item_name.empty()) {
+      return false;
+    }
+  }
+  return true;
+}
+
 // Returns true if sync items from |service1| match to sync items in |service2|.
 bool SyncItemsMatch(const app_list::AppListSyncableService* service1,
                     const app_list::AppListSyncableService* service2) {
@@ -123,6 +133,9 @@
     apps_helper::InstallApp(verifier(), i);
   }
 
+  // Allow async callbacks to run, such as App Service Mojo calls.
+  base::RunLoop().RunUntilIdle();
+
   app_list::AppListSyncableService* service =
       app_list::AppListSyncableServiceFactory::GetForProfile(verifier());
 
@@ -154,9 +167,17 @@
   std::vector<std::string> app_ids;
   for (int i = 0; i < static_cast<int>(kNumApps); ++i) {
     app_ids.push_back(apps_helper::InstallApp(profile, i));
-    service->SetPinPosition(app_ids.back(), pin_position);
+  }
+
+  // Allow async callbacks to run, such as App Service Mojo calls.
+  base::RunLoop().RunUntilIdle();
+
+  for (const std::string& app_id : app_ids) {
+    service->SetPinPosition(app_id, pin_position);
     pin_position = pin_position.CreateAfter();
   }
+  EXPECT_TRUE(SyncItemsHaveNames(service));
+
   SyncAppListHelper::GetInstance()->MoveAppToFolder(profile, app_ids[2],
                                                     "folder1");
   SyncAppListHelper::GetInstance()->MoveAppToFolder(profile, app_ids[3],
diff --git a/chrome/browser/sync/test/integration/two_client_sessions_sync_test.cc b/chrome/browser/sync/test/integration/two_client_sessions_sync_test.cc
index 353bcc8..64308f9 100644
--- a/chrome/browser/sync/test/integration/two_client_sessions_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_sessions_sync_test.cc
@@ -94,12 +94,8 @@
 
   std::vector<sync_pb::SyncEntity> entities =
       GetFakeServer()->GetSyncEntitiesByModelType(syncer::SESSIONS);
-  // Two header entities and two tab entities (one of the two has been closed
-  // but considered "free" for future recycling, i.e. not deleted).
-  ASSERT_EQ(4U, entities.size());
-  for (const auto& entity : entities) {
-    EXPECT_FALSE(entity.deleted());
-  }
+  // Two header entities and one tab entity (the other one has been deleted).
+  EXPECT_EQ(3U, entities.size());
 }
 
 IN_PROC_BROWSER_TEST_F(TwoClientSessionsSyncTest, E2E_ENABLED(AllChanged)) {
diff --git a/chrome/browser/ui/android/page_info/page_info_controller_android.cc b/chrome/browser/ui/android/page_info/page_info_controller_android.cc
index 774d31e..211424d 100644
--- a/chrome/browser/ui/android/page_info/page_info_controller_android.cc
+++ b/chrome/browser/ui/android/page_info/page_info_controller_android.cc
@@ -127,7 +127,7 @@
       user_specified_settings_to_display;
 
   for (const auto& permission : permission_info_list) {
-    if (base::ContainsValue(permissions_to_display, permission.type)) {
+    if (base::Contains(permissions_to_display, permission.type)) {
       base::Optional<ContentSetting> setting_to_display =
           GetSettingToDisplay(permission);
       if (setting_to_display) {
@@ -138,7 +138,7 @@
   }
 
   for (const auto& permission : permissions_to_display) {
-    if (base::ContainsKey(user_specified_settings_to_display, permission)) {
+    if (base::Contains(user_specified_settings_to_display, permission)) {
       base::string16 setting_title =
           PageInfoUI::PermissionTypeToUIString(permission);
 
diff --git a/chrome/browser/ui/app_list/app_list_syncable_service.cc b/chrome/browser/ui/app_list/app_list_syncable_service.cc
index 25e58d14..f810ee8 100644
--- a/chrome/browser/ui/app_list/app_list_syncable_service.cc
+++ b/chrome/browser/ui/app_list/app_list_syncable_service.cc
@@ -789,7 +789,7 @@
     SyncItem* sync_item = (iter++)->second.get();
     if (sync_item->item_type != sync_pb::AppListSpecifics::TYPE_FOLDER)
       continue;
-    if (!base::ContainsKey(parent_ids, sync_item->item_id))
+    if (!base::Contains(parent_ids, sync_item->item_id))
       DeleteSyncItem(sync_item->item_id);
   }
 }
@@ -1143,11 +1143,11 @@
 AppListSyncableService::SyncItem* AppListSyncableService::CreateSyncItem(
     const std::string& item_id,
     sync_pb::AppListSpecifics::AppListItemType item_type) {
-  DCHECK(!base::ContainsKey(sync_items_, item_id));
+  DCHECK(!base::Contains(sync_items_, item_id));
   sync_items_[item_id] = std::make_unique<SyncItem>(item_id, item_type);
 
   // In case we have pending attributes to apply, process it asynchronously.
-  if (base::ContainsKey(pending_transfer_map_, item_id)) {
+  if (base::Contains(pending_transfer_map_, item_id)) {
     base::SequencedTaskRunnerHandle::Get()->PostTask(
         FROM_HERE, base::BindOnce(&AppListSyncableService::ApplyAppAttributes,
                                   weak_ptr_factory_.GetWeakPtr(), item_id,
diff --git a/chrome/browser/ui/app_list/arc/arc_app_list_prefs.cc b/chrome/browser/ui/app_list/arc/arc_app_list_prefs.cc
index d39666049..cb6ae95 100644
--- a/chrome/browser/ui/app_list/arc/arc_app_list_prefs.cc
+++ b/chrome/browser/ui/app_list/arc/arc_app_list_prefs.cc
@@ -1747,7 +1747,7 @@
 
   for (const auto& package : packages) {
     AddOrUpdatePackagePrefs(*package);
-    if (!base::ContainsKey(old_packages, package->package_name)) {
+    if (!base::Contains(old_packages, package->package_name)) {
       for (auto& observer : observer_list_)
         observer.OnPackageInstalled(*package);
     }
@@ -1755,7 +1755,7 @@
   }
 
   for (const auto& package_name : old_packages) {
-    if (!base::ContainsKey(current_packages, package_name)) {
+    if (!base::Contains(current_packages, package_name)) {
       RemovePackageFromPrefs(package_name);
       for (auto& observer : observer_list_)
         observer.OnPackageRemoved(package_name, false);
diff --git a/chrome/browser/ui/app_list/arc/arc_app_unittest.cc b/chrome/browser/ui/app_list/arc/arc_app_unittest.cc
index d70e101..bdc29dd 100644
--- a/chrome/browser/ui/app_list/arc/arc_app_unittest.cc
+++ b/chrome/browser/ui/app_list/arc/arc_app_unittest.cc
@@ -328,7 +328,7 @@
     // In principle, order of items is not defined.
     for (const auto& app : apps) {
       const std::string id = ArcAppTest::GetAppId(app);
-      EXPECT_TRUE(base::ContainsValue(ids, id));
+      EXPECT_TRUE(base::Contains(ids, id));
       std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(id);
       ASSERT_NE(nullptr, app_info.get());
       EXPECT_EQ(app.name, app_info->name);
@@ -342,7 +342,7 @@
 
     for (auto& shortcut : shortcuts) {
       const std::string id = ArcAppTest::GetAppId(shortcut);
-      EXPECT_TRUE(base::ContainsValue(ids, id));
+      EXPECT_TRUE(base::Contains(ids, id));
       std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(id);
       ASSERT_NE(nullptr, app_info.get());
       EXPECT_EQ(shortcut.name, app_info->name);
diff --git a/chrome/browser/ui/app_list/arc/arc_app_utils.cc b/chrome/browser/ui/app_list/arc/arc_app_utils.cc
index fb619871..9d8822cda 100644
--- a/chrome/browser/ui/app_list/arc/arc_app_utils.cc
+++ b/chrome/browser/ui/app_list/arc/arc_app_utils.cc
@@ -664,7 +664,7 @@
 }
 
 bool Intent::HasExtraParam(const std::string& extra_param) const {
-  return base::ContainsValue(extra_params_, extra_param);
+  return base::Contains(extra_params_, extra_param);
 }
 
 }  // namespace arc
diff --git a/chrome/browser/ui/app_list/arc/arc_package_syncable_service.cc b/chrome/browser/ui/app_list/arc/arc_package_syncable_service.cc
index 4f6b27a..f4631dd 100644
--- a/chrome/browser/ui/app_list/arc/arc_package_syncable_service.cc
+++ b/chrome/browser/ui/app_list/arc/arc_package_syncable_service.cc
@@ -159,7 +159,7 @@
     std::unique_ptr<ArcSyncItem> sync_item(
         CreateSyncItemFromSyncData(sync_data));
     const std::string& package_name = sync_item->package_name;
-    if (!base::ContainsKey(local_package_set, package_name)) {
+    if (!base::Contains(local_package_set, package_name)) {
       pending_install_items_[package_name] = std::move(sync_item);
       InstallPackage(pending_install_items_[package_name].get());
     } else {
@@ -171,7 +171,7 @@
   // Creates sync items for local unsynced packages.
   syncer::SyncChangeList change_list;
   for (const auto& local_package_name : local_packages) {
-    if (base::ContainsKey(sync_items_, local_package_name))
+    if (base::Contains(sync_items_, local_package_name))
       continue;
 
     if (!ShouldSyncPackage(local_package_name))
diff --git a/chrome/browser/ui/app_list/page_break_constants.cc b/chrome/browser/ui/app_list/page_break_constants.cc
index de876e3..b4863a3 100644
--- a/chrome/browser/ui/app_list/page_break_constants.cc
+++ b/chrome/browser/ui/app_list/page_break_constants.cc
@@ -21,7 +21,7 @@
 
 // Returns true if |item_id| is of a default-installed page break item.
 bool IsDefaultPageBreakItem(const std::string& item_id) {
-  return base::ContainsValue(kDefaultPageBreakAppIds, item_id);
+  return base::Contains(kDefaultPageBreakAppIds, item_id);
 }
 
 }  // namespace app_list
diff --git a/chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.cc b/chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.cc
index 5dfe9fff..616c0c54 100644
--- a/chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.cc
+++ b/chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.cc
@@ -182,7 +182,7 @@
 
 bool ChromeKeyboardControllerClient::IsEnableFlagSet(
     const keyboard::KeyboardEnableFlag& flag) {
-  return base::ContainsKey(keyboard_enable_flags_, flag);
+  return base::Contains(keyboard_enable_flags_, flag);
 }
 
 void ChromeKeyboardControllerClient::ReloadKeyboardIfNeeded() {
diff --git a/chrome/browser/ui/ash/launcher/internal_app_window_shelf_controller.cc b/chrome/browser/ui/ash/launcher/internal_app_window_shelf_controller.cc
index 171bbb3..15fa2d1 100644
--- a/chrome/browser/ui/ash/launcher/internal_app_window_shelf_controller.cc
+++ b/chrome/browser/ui/ash/launcher/internal_app_window_shelf_controller.cc
@@ -91,7 +91,7 @@
     return;
 
   // Skip OnWindowVisibilityChanged for ancestors/descendants.
-  if (!base::ContainsValue(observed_windows_, window))
+  if (!base::Contains(observed_windows_, window))
     return;
 
   ash::ShelfID shelf_id =
diff --git a/chrome/browser/ui/ash/launcher/multi_profile_browser_status_monitor.cc b/chrome/browser/ui/ash/launcher/multi_profile_browser_status_monitor.cc
index dbd3aa2..e736894 100644
--- a/chrome/browser/ui/ash/launcher/multi_profile_browser_status_monitor.cc
+++ b/chrome/browser/ui/ash/launcher/multi_profile_browser_status_monitor.cc
@@ -69,7 +69,7 @@
 
 void MultiProfileBrowserStatusMonitor::AddV1AppToShelf(Browser* browser) {
   DCHECK(browser->is_type_popup() && browser->is_app());
-  DCHECK(!base::ContainsValue(app_list_, browser));
+  DCHECK(!base::Contains(app_list_, browser));
   app_list_.push_back(browser);
   if (multi_user_util::IsProfileFromActiveUser(browser->profile())) {
     BrowserStatusMonitor::AddV1AppToShelf(browser);
diff --git a/chrome/browser/ui/ash/media_client_impl.cc b/chrome/browser/ui/ash/media_client_impl.cc
index 5c0c0b5..f497e38 100644
--- a/chrome/browser/ui/ash/media_client_impl.cc
+++ b/chrome/browser/ui/ash/media_client_impl.cc
@@ -226,7 +226,7 @@
     ui::MediaKeysListener::Delegate* delegate) {
   auto it = media_key_delegates_.find(context);
 
-  DCHECK(!base::ContainsKey(media_key_delegates_, context) ||
+  DCHECK(!base::Contains(media_key_delegates_, context) ||
          it->second == delegate);
 
   media_key_delegates_.emplace(context, delegate);
@@ -237,7 +237,7 @@
 void MediaClientImpl::DisableCustomMediaKeyHandler(
     content::BrowserContext* context,
     ui::MediaKeysListener::Delegate* delegate) {
-  if (!base::ContainsKey(media_key_delegates_, context))
+  if (!base::Contains(media_key_delegates_, context))
     return;
 
   auto it = media_key_delegates_.find(context);
diff --git a/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc b/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc
index a208623e..eec89eba 100644
--- a/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc
+++ b/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc
@@ -341,7 +341,7 @@
   tester.ExpectTotalCount(kClickThroughPosition, 4);
 
   // Requests to show popups not on the list should do nothing.
-  EXPECT_FALSE(base::ContainsValue(ids, 5));
+  EXPECT_FALSE(base::Contains(ids, 5));
   popup_blocker->ShowBlockedPopup(5, disposition);
   tester.ExpectTotalCount(kClickThroughPosition, 4);
 }
diff --git a/chrome/browser/ui/blocked_content/tab_under_blocker_browsertest.cc b/chrome/browser/ui/blocked_content/tab_under_blocker_browsertest.cc
index 24299709..6ff5ccd3 100644
--- a/chrome/browser/ui/blocked_content/tab_under_blocker_browsertest.cc
+++ b/chrome/browser/ui/blocked_content/tab_under_blocker_browsertest.cc
@@ -80,7 +80,7 @@
 #if defined(OS_ANDROID)
     return false;
 #else
-    return base::ContainsValue(
+    return base::Contains(
         FramebustBlockTabHelper::FromWebContents(web_contents)->blocked_urls(),
         url);
 #endif
diff --git a/chrome/browser/ui/content_settings/framebust_block_browsertest.cc b/chrome/browser/ui/content_settings/framebust_block_browsertest.cc
index a50e70f..1965154 100644
--- a/chrome/browser/ui/content_settings/framebust_block_browsertest.cc
+++ b/chrome/browser/ui/content_settings/framebust_block_browsertest.cc
@@ -261,8 +261,8 @@
                                             redirect_url.spec().c_str())),
       base::NullCallback());
   block_waiter.Run();
-  EXPECT_TRUE(base::ContainsValue(GetFramebustTabHelper()->blocked_urls(),
-                                  redirect_url));
+  EXPECT_TRUE(
+      base::Contains(GetFramebustTabHelper()->blocked_urls(), redirect_url));
 }
 
 IN_PROC_BROWSER_TEST_F(FramebustBlockBrowserTest,
@@ -351,8 +351,8 @@
                                             redirect_url.spec().c_str())),
       base::NullCallback());
   block_waiter.Run();
-  EXPECT_TRUE(base::ContainsValue(GetFramebustTabHelper()->blocked_urls(),
-                                  redirect_url));
+  EXPECT_TRUE(
+      base::Contains(GetFramebustTabHelper()->blocked_urls(), redirect_url));
 
   // Now, navigate away and check that the UI went away.
   ui_test_utils::NavigateToURL(browser(),
diff --git a/chrome/browser/ui/input_method/input_method_engine.cc b/chrome/browser/ui/input_method/input_method_engine.cc
index 5ab21b2d..9a9d314 100644
--- a/chrome/browser/ui/input_method/input_method_engine.cc
+++ b/chrome/browser/ui/input_method/input_method_engine.cc
@@ -286,14 +286,13 @@
                                                            ui::VKEY_RETURN};
   if (ui_event->GetDomKey().IsCharacter() && !ui_event->IsControlDown() &&
       !ui_event->IsCommandDown()) {
-    return !base::ContainsValue(invalid_character_keycodes,
-                                ui_event->key_code());
+    return !base::Contains(invalid_character_keycodes, ui_event->key_code());
   }
 
   // Whitelists Backspace key and arrow keys.
   std::vector<ui::KeyboardCode> whitelist_keycodes{
       ui::VKEY_BACK, ui::VKEY_LEFT, ui::VKEY_RIGHT, ui::VKEY_UP, ui::VKEY_DOWN};
-  return base::ContainsValue(whitelist_keycodes, ui_event->key_code());
+  return base::Contains(whitelist_keycodes, ui_event->key_code());
 }
 
 }  // namespace input_method
diff --git a/chrome/browser/ui/login/login_handler_test_utils.cc b/chrome/browser/ui/login/login_handler_test_utils.cc
index 097a9c6..cb91b01b5 100644
--- a/chrome/browser/ui/login/login_handler_test_utils.cc
+++ b/chrome/browser/ui/login/login_handler_test_utils.cc
@@ -33,7 +33,7 @@
 }
 
 void LoginPromptBrowserTestObserver::AddHandler(LoginHandler* handler) {
-  ASSERT_FALSE(base::ContainsValue(handlers_, handler));
+  ASSERT_FALSE(base::Contains(handlers_, handler));
   handlers_.push_back(handler);
 }
 
diff --git a/chrome/browser/ui/media_router/cast_modes_with_media_sources.cc b/chrome/browser/ui/media_router/cast_modes_with_media_sources.cc
index 686173b0..12308fa6 100644
--- a/chrome/browser/ui/media_router/cast_modes_with_media_sources.cc
+++ b/chrome/browser/ui/media_router/cast_modes_with_media_sources.cc
@@ -31,8 +31,8 @@
 
 bool CastModesWithMediaSources::HasSource(MediaCastMode cast_mode,
                                           const MediaSource& source) const {
-  return base::ContainsKey(cast_modes_, cast_mode)
-             ? base::ContainsKey(cast_modes_.at(cast_mode), source)
+  return base::Contains(cast_modes_, cast_mode)
+             ? base::Contains(cast_modes_.at(cast_mode), source)
              : false;
 }
 
diff --git a/chrome/browser/ui/media_router/media_router_ui_base.cc b/chrome/browser/ui/media_router/media_router_ui_base.cc
index 1abc6fee..b500b88 100644
--- a/chrome/browser/ui/media_router/media_router_ui_base.cc
+++ b/chrome/browser/ui/media_router/media_router_ui_base.cc
@@ -184,8 +184,7 @@
   if (start_presentation_context_) {
     bool presentation_sinks_available = std::any_of(
         sinks_.begin(), sinks_.end(), [](const MediaSinkWithCastModes& sink) {
-          return base::ContainsKey(sink.cast_modes,
-                                   MediaCastMode::PRESENTATION);
+          return base::Contains(sink.cast_modes, MediaCastMode::PRESENTATION);
         });
     if (presentation_sinks_available) {
       start_presentation_context_->InvokeErrorCallback(
diff --git a/chrome/browser/ui/media_router/query_result_manager.cc b/chrome/browser/ui/media_router/query_result_manager.cc
index f8630f9a..b42a786 100644
--- a/chrome/browser/ui/media_router/query_result_manager.cc
+++ b/chrome/browser/ui/media_router/query_result_manager.cc
@@ -165,7 +165,7 @@
     return;
 
   for (const MediaSource& source : cast_mode_it->second) {
-    if (!base::ContainsValue(new_sources, source)) {
+    if (!base::Contains(new_sources, source)) {
       sinks_observers_.erase(source);
       SetSinksCompatibleWithSource(cast_mode, source, std::vector<MediaSink>());
     }
@@ -177,7 +177,7 @@
     const std::vector<MediaSource>& sources,
     const url::Origin& origin) {
   for (const MediaSource& source : sources) {
-    if (!base::ContainsKey(sinks_observers_, source)) {
+    if (!base::Contains(sinks_observers_, source)) {
       auto observer = std::make_unique<MediaSourceMediaSinksObserver>(
           cast_mode, source, origin, router_, this);
       observer->Init();
@@ -200,7 +200,7 @@
        /* no-op */) {
     const MediaSink::Id& sink_id = it->first;
     CastModesWithMediaSources& sources_for_sink = it->second;
-    if (!base::ContainsKey(new_sink_ids, sink_id))
+    if (!base::Contains(new_sink_ids, sink_id))
       sources_for_sink.RemoveSource(cast_mode, source);
     if (sources_for_sink.IsEmpty()) {
       sinks_with_sources_.erase(it++);
@@ -249,12 +249,12 @@
   bool has_cast_mode = cast_mode_it != cast_mode_sources_.end();
   // If a source has already been registered, then it must be associated with
   // |cast_mode|.
-  return std::find_if(
-             sources.begin(), sources.end(), [=](const MediaSource& source) {
-               return base::ContainsKey(sinks_observers_, source) &&
-                      (!has_cast_mode ||
-                       !base::ContainsValue(cast_mode_it->second, source));
-             }) == sources.end();
+  return std::find_if(sources.begin(), sources.end(),
+                      [=](const MediaSource& source) {
+                        return base::Contains(sinks_observers_, source) &&
+                               (!has_cast_mode ||
+                                !base::Contains(cast_mode_it->second, source));
+                      }) == sources.end();
 }
 
 void QueryResultManager::NotifyOnResultsUpdated() {
@@ -265,7 +265,7 @@
     sinks.push_back(sink_with_cast_modes);
   }
   for (const auto& sink : all_sinks_) {
-    if (!base::ContainsKey(sinks_with_sources_, sink.id()))
+    if (!base::Contains(sinks_with_sources_, sink.id()))
       sinks.push_back(MediaSinkWithCastModes(sink));
   }
   for (QueryResultManager::Observer& observer : observers_)
diff --git a/chrome/browser/ui/media_router/query_result_manager_unittest.cc b/chrome/browser/ui/media_router/query_result_manager_unittest.cc
index 7a960ef..8bd1b592 100644
--- a/chrome/browser/ui/media_router/query_result_manager_unittest.cc
+++ b/chrome/browser/ui/media_router/query_result_manager_unittest.cc
@@ -81,7 +81,7 @@
     return false;
 
   for (size_t i = 0; i < expected.size(); ++i) {
-    if (!base::ContainsValue(arg, expected[i]))
+    if (!base::Contains(arg, expected[i]))
       return false;
   }
   return true;
@@ -121,7 +121,7 @@
 
   cast_modes = query_result_manager_.GetSupportedCastModes();
   EXPECT_EQ(1u, cast_modes.size());
-  EXPECT_TRUE(base::ContainsKey(cast_modes, MediaCastMode::PRESENTATION));
+  EXPECT_TRUE(base::Contains(cast_modes, MediaCastMode::PRESENTATION));
   actual_sources =
       query_result_manager_.GetSourcesForCastMode(MediaCastMode::PRESENTATION);
   EXPECT_EQ(1u, actual_sources.size());
@@ -139,7 +139,7 @@
 
   cast_modes = query_result_manager_.GetSupportedCastModes();
   EXPECT_EQ(1u, cast_modes.size());
-  EXPECT_TRUE(base::ContainsKey(cast_modes, MediaCastMode::PRESENTATION));
+  EXPECT_TRUE(base::Contains(cast_modes, MediaCastMode::PRESENTATION));
   actual_sources =
       query_result_manager_.GetSourcesForCastMode(MediaCastMode::PRESENTATION);
   EXPECT_EQ(1u, actual_sources.size());
@@ -383,11 +383,10 @@
   const auto& cast_mode_sources = query_result_manager_.cast_mode_sources_;
   const auto& presentation_sources =
       cast_mode_sources.at(MediaCastMode::PRESENTATION);
-  EXPECT_TRUE(
-      base::ContainsKey(cast_mode_sources, MediaCastMode::PRESENTATION));
+  EXPECT_TRUE(base::Contains(cast_mode_sources, MediaCastMode::PRESENTATION));
   EXPECT_EQ(presentation_sources.size(), 1u);
   EXPECT_EQ(presentation_sources.at(0), source);
-  EXPECT_FALSE(base::ContainsKey(cast_mode_sources, MediaCastMode::TAB_MIRROR));
+  EXPECT_FALSE(base::Contains(cast_mode_sources, MediaCastMode::TAB_MIRROR));
 }
 
 }  // namespace media_router
diff --git a/chrome/browser/ui/permission_bubble/mock_permission_prompt_factory.cc b/chrome/browser/ui/permission_bubble/mock_permission_prompt_factory.cc
index 87032049..8bb4fa5 100644
--- a/chrome/browser/ui/permission_bubble/mock_permission_prompt_factory.cc
+++ b/chrome/browser/ui/permission_bubble/mock_permission_prompt_factory.cc
@@ -70,11 +70,11 @@
 }
 
 bool MockPermissionPromptFactory::RequestTypeSeen(PermissionRequestType type) {
-  return base::ContainsValue(request_types_seen_, type);
+  return base::Contains(request_types_seen_, type);
 }
 
 bool MockPermissionPromptFactory::RequestOriginSeen(const GURL& origin) {
-  return base::ContainsValue(request_origins_seen_, origin);
+  return base::Contains(request_origins_seen_, origin);
 }
 
 void MockPermissionPromptFactory::WaitForPermissionBubble() {
diff --git a/chrome/browser/ui/search_engines/template_url_table_model.cc b/chrome/browser/ui/search_engines/template_url_table_model.cc
index e08c44b..bbc62ed 100644
--- a/chrome/browser/ui/search_engines/template_url_table_model.cc
+++ b/chrome/browser/ui/search_engines/template_url_table_model.cc
@@ -127,8 +127,8 @@
   // Sanity checks for https://crbug.com/781703.
   CHECK_GE(index, 0);
   CHECK_LT(static_cast<size_t>(index), entries_.size());
-  CHECK(base::ContainsValue(template_url_service_->GetTemplateURLs(),
-                            entries_[index]))
+  CHECK(
+      base::Contains(template_url_service_->GetTemplateURLs(), entries_[index]))
       << "TemplateURLTableModel is returning a pointer to a TemplateURL "
          "that has already been freed by TemplateURLService.";
 
diff --git a/chrome/browser/ui/tabs/tab_strip_model.cc b/chrome/browser/ui/tabs/tab_strip_model.cc
index c4d3551..d283a39 100644
--- a/chrome/browser/ui/tabs/tab_strip_model.cc
+++ b/chrome/browser/ui/tabs/tab_strip_model.cc
@@ -778,7 +778,7 @@
 }
 
 const TabGroupData* TabStripModel::GetDataForGroup(TabGroupId group) const {
-  DCHECK(base::ContainsKey(group_data_, group));
+  DCHECK(base::Contains(group_data_, group));
   return group_data_.at(group).get();
 }
 
diff --git a/chrome/browser/ui/toolbar/toolbar_actions_model.cc b/chrome/browser/ui/toolbar/toolbar_actions_model.cc
index 19bfb20..0e44b87f 100644
--- a/chrome/browser/ui/toolbar/toolbar_actions_model.cc
+++ b/chrome/browser/ui/toolbar/toolbar_actions_model.cc
@@ -320,8 +320,7 @@
   CHECK(actions_initialized_);
 
   // See if we have a last known good position for this extension.
-  bool is_new_extension =
-      !base::ContainsValue(last_known_positions_, action_id);
+  bool is_new_extension = !base::Contains(last_known_positions_, action_id);
 
   // New extensions go at the right (end) of the visible extensions. Other
   // extensions go at their previous position.
@@ -437,7 +436,7 @@
 
 bool ToolbarActionsModel::IsActionPinned(const ActionId& action_id) const {
   DCHECK(base::FeatureList::IsEnabled(features::kExtensionsToolbarMenu));
-  return base::ContainsValue(pinned_action_ids_, action_id);
+  return base::Contains(pinned_action_ids_, action_id);
 }
 
 void ToolbarActionsModel::RemoveExtension(
@@ -590,7 +589,7 @@
 }
 
 bool ToolbarActionsModel::HasAction(const ActionId& action_id) const {
-  return base::ContainsValue(action_ids_, action_id);
+  return base::Contains(action_ids_, action_id);
 }
 
 void ToolbarActionsModel::IncognitoPopulate() {
@@ -689,7 +688,7 @@
   std::vector<ActionId> pref_positions = extension_prefs_->GetToolbarOrder();
   size_t pref_position_size = pref_positions.size();
   for (size_t i = 0; i < last_known_positions_.size(); ++i) {
-    if (!base::ContainsValue(pref_positions, last_known_positions_[i])) {
+    if (!base::Contains(pref_positions, last_known_positions_[i])) {
       pref_positions.push_back(last_known_positions_[i]);
     }
   }
diff --git a/chrome/browser/ui/views/media_router/cast_dialog_view.cc b/chrome/browser/ui/views/media_router/cast_dialog_view.cc
index 9c87c42f2..f2eae8a 100644
--- a/chrome/browser/ui/views/media_router/cast_dialog_view.cc
+++ b/chrome/browser/ui/views/media_router/cast_dialog_view.cc
@@ -434,17 +434,17 @@
   // supported and selected.
   switch (selected_source_) {
     case SourceType::kTab:
-      if (base::ContainsKey(sink.cast_modes, PRESENTATION))
+      if (base::Contains(sink.cast_modes, PRESENTATION))
         return base::make_optional<MediaCastMode>(PRESENTATION);
-      if (base::ContainsKey(sink.cast_modes, TAB_MIRROR))
+      if (base::Contains(sink.cast_modes, TAB_MIRROR))
         return base::make_optional<MediaCastMode>(TAB_MIRROR);
       break;
     case SourceType::kDesktop:
-      if (base::ContainsKey(sink.cast_modes, DESKTOP_MIRROR))
+      if (base::Contains(sink.cast_modes, DESKTOP_MIRROR))
         return base::make_optional<MediaCastMode>(DESKTOP_MIRROR);
       break;
     case SourceType::kLocalFile:
-      if (base::ContainsKey(sink.cast_modes, LOCAL_FILE))
+      if (base::Contains(sink.cast_modes, LOCAL_FILE))
         return base::make_optional<MediaCastMode>(LOCAL_FILE);
       break;
   }
diff --git a/chrome/browser/ui/views/media_router/media_router_views_ui_unittest.cc b/chrome/browser/ui/views/media_router/media_router_views_ui_unittest.cc
index 320289d..43b53f9b 100644
--- a/chrome/browser/ui/views/media_router/media_router_views_ui_unittest.cc
+++ b/chrome/browser/ui/views/media_router/media_router_views_ui_unittest.cc
@@ -110,7 +110,7 @@
         EXPECT_EQ(base::UTF8ToUTF16(sink.name()), ui_sink.friendly_name);
         EXPECT_EQ(UIMediaSinkState::AVAILABLE, ui_sink.state);
         EXPECT_TRUE(
-            base::ContainsKey(ui_sink.cast_modes, MediaCastMode::TAB_MIRROR));
+            base::Contains(ui_sink.cast_modes, MediaCastMode::TAB_MIRROR));
         EXPECT_EQ(sink.icon_type(), ui_sink.icon_type);
       })));
   ui_->OnResultsUpdated({sink_with_cast_modes});
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller.cc b/chrome/browser/ui/views/tabs/tab_drag_controller.cc
index 7b9fdfa..6d82690 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller.cc
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller.cc
@@ -12,9 +12,7 @@
 #include "base/auto_reset.h"
 #include "base/bind.h"
 #include "base/callback.h"
-#include "base/feature_list.h"
 #include "base/i18n/rtl.h"
-#include "base/logging.h"
 #include "base/macros.h"
 #include "base/numerics/ranges.h"
 #include "base/stl_util.h"
@@ -28,7 +26,6 @@
 #include "chrome/browser/ui/sad_tab_helper.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
-#include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/tabs/tab.h"
 #include "chrome/browser/ui/views/tabs/tab_strip.h"
@@ -435,7 +432,7 @@
                              MoveBehavior move_behavior,
                              EventSource event_source) {
   DCHECK(!tabs.empty());
-  DCHECK(base::ContainsValue(tabs, source_tab));
+  DCHECK(base::Contains(tabs, source_tab));
   source_context_ = source_context;
   was_source_maximized_ = source_context->AsView()->GetWidget()->IsMaximized();
   was_source_fullscreen_ =
@@ -476,9 +473,9 @@
       source_context_->AsView()->GetWidget()->GetNativeWindow());
 
   if (source_tab->width() > 0) {
-    offset_to_width_ratio_ =
-        float{source_tab->GetMirroredXInView(source_tab_offset)} /
-        float{source_tab->width()};
+    offset_to_width_ratio_ = static_cast<float>(
+        source_tab->GetMirroredXInView(source_tab_offset)) /
+        static_cast<float>(source_tab->width());
   }
   InitWindowCreatePoint();
   initial_selection_model_ = std::move(initial_selection_model);
@@ -546,7 +543,7 @@
     }
     current_state_ = DragState::kDraggingTabs;
     Attach(source_context_, gfx::Point());
-    if (int{drag_data_.size()} ==
+    if (static_cast<int>(drag_data_.size()) ==
         source_context_->GetTabStripModel()->count()) {
       views::Widget* widget = GetAttachedBrowserWidget();
       gfx::Rect new_bounds;
@@ -754,8 +751,8 @@
   static const int kMinimumDragDistance = 10;
   int x_offset = abs(point_in_screen.x() - start_point_in_screen_.x());
   int y_offset = abs(point_in_screen.y() - start_point_in_screen_.y());
-  return sqrt(pow(float{x_offset}, 2) + pow(float{y_offset}, 2)) >
-         kMinimumDragDistance;
+  return sqrt(pow(static_cast<float>(x_offset), 2) +
+              pow(static_cast<float>(y_offset), 2)) > kMinimumDragDistance;
 }
 
 TabDragController::Liveness TabDragController::ContinueDragging(
@@ -909,7 +906,8 @@
 
 void TabDragController::DragActiveTabStacked(
     const gfx::Point& point_in_screen) {
-  if (attached_context_->GetTabCount() != int{initial_tab_positions_.size()})
+  if (attached_context_->GetTabCount() !=
+      static_cast<int>(initial_tab_positions_.size()))
     return;  // TODO: should cancel drag if this happens.
 
   int delta = point_in_screen.x() - start_point_in_screen_.x();
@@ -959,7 +957,7 @@
     TabStripModel* attached_model = attached_context_->GetTabStripModel();
     int to_index = attached_context_->GetInsertionIndexForDraggedBounds(
         GetDraggedViewTabStripBounds(dragged_view_point), false,
-        int{drag_data_.size()}, mouse_has_ever_moved_left_,
+        static_cast<int>(drag_data_.size()), mouse_has_ever_moved_left_,
         mouse_has_ever_moved_right_);
     bool do_move = true;
     // While dragging within a tabstrip the expectation is the insertion index
@@ -1001,9 +999,6 @@
             initial_move_);
         did_layout = true;
       }
-      if (base::FeatureList::IsEnabled(features::kDragToPinTabs)) {
-        UpdatePinnednessOfDraggedTab(to_index);
-      }
       attached_model->MoveSelectedTabsTo(to_index);
 
       // Move may do nothing in certain situations (such as when dragging pinned
@@ -1160,7 +1155,6 @@
 
   std::vector<Tab*> tabs = GetTabsMatchingDraggedContents(attached_context_);
 
-  TabStripModel* attached_model = attached_context_->GetTabStripModel();
   if (tabs.empty()) {
     // Transitioning from detached to attached to a new context. Add tabs to
     // the new model.
@@ -1184,7 +1178,7 @@
     tab_strip_point.Offset(0, -mouse_offset_.y());
     int index = attached_context_->GetInsertionIndexForDraggedBounds(
         GetDraggedViewTabStripBounds(tab_strip_point), true,
-        int{drag_data_.size()}, mouse_has_ever_moved_left_,
+        static_cast<int>(drag_data_.size()), mouse_has_ever_moved_left_,
         mouse_has_ever_moved_right_);
     attach_index_ = index;
     attach_x_ = tab_strip_point.x();
@@ -1197,26 +1191,10 @@
         DCHECK_EQ(1u, drag_data_.size());
         add_types |= TabStripModel::ADD_ACTIVE;
       }
-      const int target_index = index + i;
-
-      if (base::FeatureList::IsEnabled(features::kDragToPinTabs)) {
-        if (drag_data_[i].pinned) {
-          // Attaching a tab is effectively an insertion at the target index. A
-          // pinned tab will only need to unpin if the previous tab is unpinned.
-          drag_data_[i].pinned = !CheckValidPinnedness(
-              target_index - 1, TabAnimationState::TabPinnedness::kUnpinned);
-        } else {
-          // An unpinned tab will only need to be pinned if the tab to the right
-          // of it is is pinned.
-          drag_data_[i].pinned = CheckValidPinnedness(
-              target_index, TabAnimationState::TabPinnedness::kPinned);
-        }
-      }
       if (drag_data_[i].pinned)
         add_types |= TabStripModel::ADD_PINNED;
-
-      attached_model->InsertWebContentsAt(
-          target_index, std::move(drag_data_[i].owned_contents), add_types);
+      attached_context_->GetTabStripModel()->InsertWebContentsAt(
+          index + i, std::move(drag_data_[i].owned_contents), add_types);
 
       // If a sad tab is showing, the SadTabView needs to be updated.
       SadTabHelper* sad_tab_helper =
@@ -1224,13 +1202,14 @@
       if (sad_tab_helper)
         sad_tab_helper->ReinstallInWebView();
     }
+
     tabs = GetTabsMatchingDraggedContents(attached_context_);
   }
   DCHECK_EQ(tabs.size(), drag_data_.size());
   for (size_t i = 0; i < drag_data_.size(); ++i)
     drag_data_[i].attached_tab = tabs[i];
 
-  ResetSelection(attached_model);
+  ResetSelection(attached_context_->GetTabStripModel());
 
   // This should be called after ResetSelection() in order to generate
   // bounds correctly. http://crbug.com/836004
@@ -1255,7 +1234,8 @@
   attached_context_->OwnDragController(this);
   SetTabDraggingInfo();
   attached_context_tabs_closed_tracker_ =
-      std::make_unique<DraggedTabsClosedTracker>(attached_model, this);
+      std::make_unique<DraggedTabsClosedTracker>(
+          attached_context_->GetTabStripModel(), this);
 }
 
 void TabDragController::Detach(ReleaseCapture release_capture) {
@@ -1317,7 +1297,7 @@
 void TabDragController::DetachIntoNewBrowserAndRunMoveLoop(
     const gfx::Point& point_in_screen) {
   if (attached_context_->GetTabStripModel()->count() ==
-      int{drag_data_.size()}) {
+      static_cast<int>(drag_data_.size())) {
     // All the tabs in a browser are being dragged but all the tabs weren't
     // initially being dragged. For this to happen the user would have to
     // start dragging a set of tabs, the other tabs close, then detach.
@@ -1940,12 +1920,12 @@
   // If the new tabstrip is smaller than the old resize the tabs.
   if (dragged_context_width < tab_area_width) {
     const float leading_ratio =
-        drag_bounds->front().x() / float{tab_area_width};
+        drag_bounds->front().x() / static_cast<float>(tab_area_width);
     *drag_bounds = CalculateBoundsForDraggedTabs();
 
     if (drag_bounds->back().right() < dragged_context_width) {
       const int delta_x = std::min(
-          int{leading_ratio * dragged_context_width},
+          static_cast<int>(leading_ratio * dragged_context_width),
           dragged_context_width -
               (drag_bounds->back().right() - drag_bounds->front().x()));
       OffsetX(delta_x, drag_bounds);
@@ -2111,49 +2091,6 @@
 #endif
 }
 
-void TabDragController::UpdatePinnednessOfDraggedTab(int to_index) {
-  TabStripModel* attached_model = attached_context_->GetTabStripModel();
-  const int selected_count =
-      int{attached_model->selection_model().selected_indices().size()};
-  for (int i = 0; i < selected_count; i++) {
-    const int current_index =
-        attached_model->selection_model().selected_indices()[i];
-    const int target_index = to_index + i;
-    if (current_index != target_index) {
-      // When dragging a tab to the target index, the tab is effectively
-      // swapping places with the tab at its target index.This means that
-      // if the current tab is pinned but the tab it is swapping with is
-      // not, the current tab will need to be unpinned. The opposite is
-      // true when moving an unpinned tab.
-      if (attached_model->IsTabPinned(current_index) &&
-          CheckValidPinnedness(target_index,
-                               TabAnimationState::TabPinnedness::kUnpinned)) {
-        attached_model->SetTabPinned(current_index, false);
-      } else if (!attached_model->IsTabPinned(current_index) &&
-                 CheckValidPinnedness(
-                     target_index, TabAnimationState::TabPinnedness::kPinned)) {
-        attached_model->SetTabPinned(current_index, true);
-      }
-    }
-  }
-}
-
-bool TabDragController::CheckValidPinnedness(
-    int index,
-    TabAnimationState::TabPinnedness expected_pinnedness) {
-  TabStripModel* attached_model = attached_context_->GetTabStripModel();
-
-  if (!attached_model->ContainsIndex(index))
-    return false;
-
-  TabAnimationState::TabPinnedness pinnedness =
-      attached_model->IsTabPinned(index)
-          ? TabAnimationState::TabPinnedness::kPinned
-          : TabAnimationState::TabPinnedness::kUnpinned;
-
-  return expected_pinnedness == pinnedness;
-}
-
 void TabDragController::SetDeferredTargetTabstrip(
     TabDragContext* deferred_target_context) {
 #if defined(OS_CHROMEOS)
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller.h b/chrome/browser/ui/views/tabs/tab_drag_controller.h
index 4e7846b..653d681 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller.h
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller.h
@@ -14,7 +14,6 @@
 #include "base/memory/weak_ptr.h"
 #include "base/timer/timer.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
-#include "chrome/browser/ui/views/tabs/tab_animation_state.h"
 #include "chrome/browser/ui/views/tabs/tab_drag_context.h"
 #include "chrome/browser/ui/views/tabs/tab_strip_types.h"
 #include "ui/base/models/list_selection_model.h"
@@ -495,19 +494,6 @@
   // is showing a modal).
   bool ShouldDisallowDrag(gfx::NativeWindow window);
 
-  // Helper method for TabDragController::MoveAttached to update the pinnedness
-  // of the tab being moved by checking the pinnedness of the tabs being
-  // dragged with the pinnedness of the tab at the target dragged location.
-  // TODO (crbug.com/971676): This will swap and update the pinnedness of
-  // multi-selected tabs one at a time, which is unintended.
-  void UpdatePinnednessOfDraggedTab(int to_index);
-
-  // Helper method that checks if the index is valid in the TabDragContext and
-  // the pin at the index has the expected pinned value.
-  bool CheckValidPinnedness(
-      int index,
-      TabAnimationState::TabPinnedness expected_pinnedness);
-
   EventSource event_source_;
 
   // The TabDragContext the drag originated from. This is set to null
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc b/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
index 2a9d72a..3ad4e5f 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
@@ -23,7 +23,6 @@
 #include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/test/bind_test_util.h"
-#include "base/test/scoped_feature_list.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
 #include "chrome/browser/chrome_notification_types.h"
@@ -32,7 +31,6 @@
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/browser_tabstrip.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
-#include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/native_browser_frame_factory.h"
 #include "chrome/browser/ui/views/tabs/tab.h"
@@ -423,50 +421,6 @@
 #endif  // OS_MACOSX
   }
 
-  // Set up the test environment with 4 tabs. Two pinned and two unpinned.
-  void SetUpDragToChangePinnednessTest() {
-    AddTabAndResetBrowser(browser());
-    AddTabAndResetBrowser(browser());
-    AddTabAndResetBrowser(browser());
-
-    TabStrip* tab_strip = GetTabStripForBrowser(browser());
-    TabStripModel* model = browser()->tab_strip_model();
-
-    model->SetTabPinned(0, true);
-    model->SetTabPinned(1, true);
-    StopAnimating(tab_strip);
-    ASSERT_TRUE(model->IsTabPinned(0));
-    ASSERT_TRUE(model->IsTabPinned(1));
-    ASSERT_FALSE(model->IsTabPinned(2));
-    ASSERT_FALSE(model->IsTabPinned(3));
-    EXPECT_EQ("0 1 2 3", IDString(model));
-  }
-
-  // Set up the test environment another browser with two unpinned tabs.
-  Browser* SetUpSecondBrowserWithTwoUnpinnedTabs() {
-    Browser* browser2 = CreateAnotherBrowserAndResize();
-    AddTabAndResetBrowser(browser2);
-    ResetIDs(browser2->tab_strip_model(), 100);
-    return browser2;
-  }
-
-  void SelectIndicies(TabStrip* tabstrip,
-                      TabStripModel* model,
-                      std::vector<int> indicies) {
-    DCHECK(!indicies.empty());
-    // The last tab opened will automatically be selected. Simulating a click to
-    // select the first index will ensure others are no longer selected.
-    ASSERT_TRUE(PressInput(
-        GetCenterInScreenCoordinates(tabstrip->tab_at(indicies.at(0)))));
-    ASSERT_TRUE(ReleaseInput());
-
-    if (indicies.size() > 1) {
-      for (auto it = std::next(indicies.begin()); it != indicies.end(); ++it) {
-        model->ToggleSelectionAt(*it);
-      }
-    }
-  }
-
   InputSource input_source() const {
     return strstr(GetParam(), "mouse") ?
         INPUT_SOURCE_MOUSE : INPUT_SOURCE_TOUCH;
@@ -641,23 +595,6 @@
 
   Browser* browser() const { return InProcessBrowserTest::browser(); }
 
-  void DragToSeparateWindowBeforeFirstTab(TabStrip* tabstrip_to) {
-    gfx::Point tab_left_center =
-        tabstrip_to->tab_at(0)->GetLocalBounds().left_center();
-    views::View::ConvertPointToScreen(tabstrip_to->tab_at(0), &tab_left_center);
-    ASSERT_TRUE(DragInputToAsync(tab_left_center));
-  }
-
-  void DragToSeparateWindowAfterFirstTab(TabStrip* tabstrip_to) {
-    gfx::Point tab_right_center =
-        tabstrip_to->tab_at(0)->GetLocalBounds().right_center();
-    views::View::ConvertPointToScreen(tabstrip_to->tab_at(0),
-                                      &tab_right_center);
-    ASSERT_TRUE(DragInputToAsync(tab_right_center));
-  }
-
-  base::test::ScopedFeatureList scoped_feature_list_;
-
  private:
 #if defined(OS_CHROMEOS)
   // The root window for the event generator.
@@ -669,6 +606,12 @@
 
 // Creates a browser with two tabs, drags the second to the first.
 IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest, DragInSameWindow) {
+  // TODO(sky): this won't work with touch as it requires a long press.
+  if (input_source() == INPUT_SOURCE_TOUCH) {
+    VLOG(1) << "Test is DISABLED for touch input.";
+    return;
+  }
+
   AddTabAndResetBrowser(browser());
 
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
@@ -690,160 +633,6 @@
   EXPECT_FALSE(tab_strip->GetWidget()->HasCapture());
 }
 
-// Creates a browser with four tabs two pinned two unpinned. With the
-// kDragToPinTabs flag off, dragging tabs between pinned and unpinned will have
-// no effect
-IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
-                       DragInSameWindow_CannotDragBetweenPinnedness) {
-  scoped_feature_list_.InitAndDisableFeature(features::kDragToPinTabs);
-  SetUpDragToChangePinnednessTest();
-
-  TabStrip* tab_strip = GetTabStripForBrowser(browser());
-  TabStripModel* model = browser()->tab_strip_model();
-
-  // Dragging an unpinned tab before a pinned tab will have no effect.
-  ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(2))));
-  ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(1))));
-  ASSERT_TRUE(ReleaseInput());
-  EXPECT_EQ("0 1 2 3", IDString(model));
-  EXPECT_TRUE(model->IsTabPinned(0));
-  EXPECT_TRUE(model->IsTabPinned(1));
-  EXPECT_FALSE(model->IsTabPinned(2));
-  EXPECT_FALSE(model->IsTabPinned(3));
-
-  // Dragging a pinned tab after an unpinned tab will have no effect.
-  ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(1))));
-  ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(2))));
-  ASSERT_TRUE(ReleaseInput());
-  EXPECT_EQ("0 1 2 3", IDString(model));
-  EXPECT_TRUE(model->IsTabPinned(0));
-  EXPECT_TRUE(model->IsTabPinned(1));
-  EXPECT_FALSE(model->IsTabPinned(2));
-  EXPECT_FALSE(model->IsTabPinned(3));
-}
-
-// Creates a browser with four tabs two pinned two unpinned. With the
-// kDragToPinTabs flag on, dragging unpinned tabs before a pinned tab will
-// pin the unpinned tab.
-IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
-                       DragInSameWindow_DragUnpinnedBeforePinned) {
-  scoped_feature_list_.InitAndEnableFeature(features::kDragToPinTabs);
-  SetUpDragToChangePinnednessTest();
-
-  TabStrip* tab_strip = GetTabStripForBrowser(browser());
-  TabStripModel* model = browser()->tab_strip_model();
-
-  SelectIndicies(tab_strip, model, std::vector<int>{2, 3});
-  ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(2))));
-  ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(0))));
-  ASSERT_TRUE(ReleaseInput());
-  StopAnimating(tab_strip);
-  EXPECT_EQ("2 3 0 1", IDString(model));
-  EXPECT_TRUE(model->IsTabPinned(0));
-  EXPECT_TRUE(model->IsTabPinned(1));
-  EXPECT_TRUE(model->IsTabPinned(2));
-  EXPECT_TRUE(model->IsTabPinned(3));
-}
-
-// Creates a browser with four tabs two pinned two unpinned. With the
-// kDragToPinTabs flag on, dragging pinned tabs after the unpinned tab will
-// unpin the pinned tab.
-IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
-                       DragInSameWindow_DragPinnedAfterUnpinned) {
-  scoped_feature_list_.InitAndEnableFeature(features::kDragToPinTabs);
-  SetUpDragToChangePinnednessTest();
-
-  TabStrip* tab_strip = GetTabStripForBrowser(browser());
-  TabStripModel* model = browser()->tab_strip_model();
-
-  SelectIndicies(tab_strip, model, std::vector<int>{0, 1});
-
-  // TODO (crbug.com/965681): Investigate and fix the dragging case for dragging
-  // pinned tabs to after unpinned tabs.
-  gfx::Point tab_2 = GetCenterInScreenCoordinates(tab_strip->tab_at(2));
-  gfx::Point tab_2_left_center =
-      tab_strip->tab_at(2)->GetLocalBounds().left_center();
-  views::View::ConvertPointToScreen(tab_strip->tab_at(2), &tab_2_left_center);
-  ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(0))));
-  ASSERT_TRUE(DragInputTo(tab_2_left_center));
-  ASSERT_TRUE(DragInputTo(tab_2));
-  ASSERT_TRUE(ReleaseInput());
-  StopAnimating(tab_strip);
-  EXPECT_FALSE(model->IsTabPinned(0));
-  EXPECT_FALSE(model->IsTabPinned(1));
-  EXPECT_FALSE(model->IsTabPinned(2));
-  EXPECT_FALSE(model->IsTabPinned(3));
-}
-
-// Creates one browser with a single pinned tab as well as another browser
-// (browser2) with one pinned tab and one unpinned tab. With the kDragToPinTabs
-// flag on, dragging a both tabs from browser2 to the other browser to the left
-// of the first pinned tab will pin both tabs dragged.
-IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
-                       DragToPinInNewWindow) {
-  scoped_feature_list_.InitAndEnableFeature(features::kDragToPinTabs);
-
-  TabStrip* tab_strip = GetTabStripForBrowser(browser());
-  TabStripModel* model = browser()->tab_strip_model();
-  model->SetTabPinned(0, true);
-  StopAnimating(tab_strip);
-
-  // Create another browser.
-  Browser* browser2 = SetUpSecondBrowserWithTwoUnpinnedTabs();
-  TabStrip* tab_strip2 = GetTabStripForBrowser(browser2);
-  browser2->tab_strip_model()->SetTabPinned(0, true);
-  StopAnimating(tab_strip2);
-
-  SelectIndicies(tab_strip2, browser2->tab_strip_model(),
-                 std::vector<int>{0, 1});
-
-  // This should be moving the entire window.
-  DragTabAndNotify(tab_strip2,
-                   base::BindOnce(&DetachToBrowserTabDragControllerTest::
-                                      DragToSeparateWindowBeforeFirstTab,
-                                  base::Unretained(this), tab_strip));
-  ASSERT_TRUE(ReleaseInput());
-
-  EXPECT_EQ(3, model->count());
-  EXPECT_TRUE(model->IsTabPinned(0));
-  EXPECT_TRUE(model->IsTabPinned(1));
-  EXPECT_TRUE(model->IsTabPinned(2));
-}
-
-// Creates one browser with a single unpinned tab as well as another browser
-// (browser2) with one pinned tab and one unpinned tab. With the kDragToPinTabs
-// flag on, dragging a both tabs from browser2 to the other browser to the right
-// of the unpinned tab will unpin both tabs dragged.
-IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
-                       DragToPinEnabled_DragToUnpinInNewWindow) {
-  scoped_feature_list_.InitAndEnableFeature(features::kDragToPinTabs);
-
-  TabStrip* tab_strip = GetTabStripForBrowser(browser());
-  TabStripModel* model = browser()->tab_strip_model();
-
-  // Create another browser.
-  Browser* browser2 = SetUpSecondBrowserWithTwoUnpinnedTabs();
-  TabStrip* tab_strip2 = GetTabStripForBrowser(browser2);
-  browser2->tab_strip_model()->SetTabPinned(0, true);
-  StopAnimating(tab_strip2);
-
-  SelectIndicies(tab_strip2, browser2->tab_strip_model(),
-                 std::vector<int>{0, 1});
-
-  // This should be moving the entire window.
-  DragTabAndNotify(tab_strip2,
-                   base::BindOnce(&DetachToBrowserTabDragControllerTest::
-                                      DragToSeparateWindowAfterFirstTab,
-                                  base::Unretained(this), tab_strip));
-
-  ASSERT_TRUE(ReleaseInput());
-
-  EXPECT_EQ(3, model->count());
-  EXPECT_FALSE(model->IsTabPinned(0));
-  EXPECT_FALSE(model->IsTabPinned(1));
-  EXPECT_FALSE(model->IsTabPinned(2));
-}
-
 #if defined(USE_AURA)
 bool SubtreeShouldBeExplored(aura::Window* window,
                              const gfx::Point& local_point) {
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index d8dcf65..4be7369 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -18,6 +18,7 @@
 #include "base/containers/flat_map.h"
 #include "base/feature_list.h"
 #include "base/macros.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/metrics/user_metrics.h"
 #include "base/no_destructor.h"
@@ -341,7 +342,7 @@
       }
     }
     DCHECK(!tabs.empty());
-    DCHECK(base::ContainsValue(tabs, tab));
+    DCHECK(base::Contains(tabs, tab));
     ui::ListSelectionModel selection_model;
     if (!original_selection.IsSelected(model_index))
       selection_model = original_selection;
@@ -1029,6 +1030,14 @@
   // model and tabstrip are in sync.
   if (!drag_context_->IsMutating() && drag_context_->IsDraggingWindow())
     EndDrag(END_DRAG_COMPLETE);
+
+  Profile* profile = controller()->GetProfile();
+  if (profile) {
+    if (profile->IsGuestSession())
+      base::UmaHistogramCounts100("Tab.Count.Guest", tab_count());
+    else if (profile->IsIncognitoProfile())
+      base::UmaHistogramCounts100("Tab.Count.Incognito", tab_count());
+  }
 }
 
 void TabStrip::MoveTab(int from_model_index,
diff --git a/chrome/browser/ui/views/tabs/tab_strip_layout_helper.cc b/chrome/browser/ui/views/tabs/tab_strip_layout_helper.cc
index 2f36aef..20c43fe 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_layout_helper.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip_layout_helper.cc
@@ -124,7 +124,7 @@
     const bool pinned = i < num_pinned_tabs;
     base::Optional<TabGroupId> group = tab_to_group_mapping[i];
     if (group.has_value() &&
-        !base::ContainsKey(headers_already_added, group.value())) {
+        !base::Contains(headers_already_added, group.value())) {
       // Start of a group.
       slots.push_back(TabSlot::CreateForGroupHeader(group.value(), pinned));
       headers_already_added.insert(group.value());
diff --git a/chrome/browser/ui/views/tabs/window_finder_ozone.cc b/chrome/browser/ui/views/tabs/window_finder_ozone.cc
index f290223..267c889 100644
--- a/chrome/browser/ui/views/tabs/window_finder_ozone.cc
+++ b/chrome/browser/ui/views/tabs/window_finder_ozone.cc
@@ -18,5 +18,5 @@
     if (views::Widget::GetWidgetForNativeWindow(window))
       break;
   }
-  return (window && !base::ContainsKey(ignore, window)) ? window : nullptr;
+  return (window && !base::Contains(ignore, window)) ? window : nullptr;
 }
diff --git a/chrome/browser/ui/views/webauthn/hover_list_view.cc b/chrome/browser/ui/views/webauthn/hover_list_view.cc
index 14a48c60..3bc805be 100644
--- a/chrome/browser/ui/views/webauthn/hover_list_view.cc
+++ b/chrome/browser/ui/views/webauthn/hover_list_view.cc
@@ -222,7 +222,7 @@
 }
 
 void HoverListView::AddListItemView(int item_tag) {
-  CHECK(!base::ContainsKey(tags_to_list_item_views_, item_tag));
+  CHECK(!base::Contains(tags_to_list_item_views_, item_tag));
   if (placeholder_list_item_view_) {
     RemoveListItemView(*placeholder_list_item_view_);
     placeholder_list_item_view_.reset();
diff --git a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
index 15e03543..40d1619 100644
--- a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
@@ -184,7 +184,7 @@
   if (input_method.empty())
     return;
 
-  if (!base::ContainsValue(*input_methods, input_method))
+  if (!base::Contains(*input_methods, input_method))
     input_methods->insert(input_methods->begin(), input_method);
 }
 
diff --git a/chrome/browser/ui/webui/chromeos/login/l10n_util.cc b/chrome/browser/ui/webui/chromeos/login/l10n_util.cc
index 69825fc..c3dbe6c 100644
--- a/chrome/browser/ui/webui/chromeos/login/l10n_util.cc
+++ b/chrome/browser/ui/webui/chromeos/login/l10n_util.cc
@@ -155,7 +155,7 @@
     if (lang.empty() || lang == language_id)
       continue;
 
-    if (base::ContainsValue(base_language_codes, language_id)) {
+    if (base::Contains(base_language_codes, language_id)) {
       // Language is supported. No need to replace
       continue;
     }
@@ -163,7 +163,7 @@
     if (!l10n_util::CheckAndResolveLocale(language_id, &resolved_locale))
       continue;
 
-    if (!base::ContainsValue(base_language_codes, resolved_locale)) {
+    if (!base::Contains(base_language_codes, resolved_locale)) {
       // Resolved locale is not supported.
       continue;
     }
@@ -186,7 +186,7 @@
        it != language_codes.end(); ++it) {
      // Exclude the language which is not in |base_langauge_codes| even it has
      // input methods.
-     if (!base::ContainsValue(base_language_codes, *it))
+     if (!base::Contains(base_language_codes, *it))
        continue;
 
      const base::string16 display_name =
diff --git a/chrome/browser/ui/webui/chromeos/login/oobe_display_chooser.cc b/chrome/browser/ui/webui/chromeos/login/oobe_display_chooser.cc
index a0e563f..dd76b24 100644
--- a/chrome/browser/ui/webui/chromeos/login/oobe_display_chooser.cc
+++ b/chrome/browser/ui/webui/chromeos/login/oobe_display_chooser.cc
@@ -37,7 +37,7 @@
 // Returns true if |vendor_id| is a valid vendor id that may be made the primary
 // display.
 bool IsWhiteListedVendorId(uint16_t vendor_id) {
-  return base::ContainsValue(kDeviceIds, vendor_id);
+  return base::Contains(kDeviceIds, vendor_id);
 }
 
 }  // namespace
diff --git a/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc b/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc
index 691f668..c83c559 100644
--- a/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc
+++ b/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc
@@ -271,7 +271,7 @@
 std::string GetDisplayType(const GURL& url) {
   std::string path = url.path().size() ? url.path().substr(1) : "";
 
-  if (!base::ContainsValue(kKnownDisplayTypes, path)) {
+  if (!base::Contains(kKnownDisplayTypes, path)) {
     LOG(ERROR) << "Unknown display type '" << path << "'. Setting default.";
     return OobeUI::kLoginDisplay;
   }
diff --git a/chrome/browser/ui/webui/cookies_tree_model_util.cc b/chrome/browser/ui/webui/cookies_tree_model_util.cc
index 495e6b7..799d6fb 100644
--- a/chrome/browser/ui/webui/cookies_tree_model_util.cc
+++ b/chrome/browser/ui/webui/cookies_tree_model_util.cc
@@ -185,13 +185,13 @@
       dict->SetString(kKeyOrigin, file_system_info.origin.Serialize());
       dict->SetString(
           kKeyPersistent,
-          base::ContainsKey(file_system_info.usage_map, kPerm)
+          base::Contains(file_system_info.usage_map, kPerm)
               ? base::UTF16ToUTF8(ui::FormatBytes(
                     file_system_info.usage_map.find(kPerm)->second))
               : l10n_util::GetStringUTF8(IDS_COOKIES_FILE_SYSTEM_USAGE_NONE));
       dict->SetString(
           kKeyTemporary,
-          base::ContainsKey(file_system_info.usage_map, kTemp)
+          base::Contains(file_system_info.usage_map, kTemp)
               ? base::UTF16ToUTF8(ui::FormatBytes(
                     file_system_info.usage_map.find(kTemp)->second))
               : l10n_util::GetStringUTF8(IDS_COOKIES_FILE_SYSTEM_USAGE_NONE));
diff --git a/chrome/browser/ui/webui/local_discovery/local_discovery_ui_browsertest.cc b/chrome/browser/ui/webui/local_discovery/local_discovery_ui_browsertest.cc
index fe9a0f7..98817b8 100644
--- a/chrome/browser/ui/webui/local_discovery/local_discovery_ui_browsertest.cc
+++ b/chrome/browser/ui/webui/local_discovery/local_discovery_ui_browsertest.cc
@@ -501,9 +501,9 @@
         }));
     EXPECT_TRUE(WebUIBrowserTest::RunJavascriptTest("registerBegin"));
     run_loop.Run();
-    EXPECT_TRUE(base::ContainsKey(served_urls, GURL(kURLInfo)));
-    EXPECT_TRUE(base::ContainsKey(served_urls, GURL(kURLRegisterStart)));
-    EXPECT_TRUE(base::ContainsKey(served_urls, GURL(kURLRegisterClaimToken)));
+    EXPECT_TRUE(base::Contains(served_urls, GURL(kURLInfo)));
+    EXPECT_TRUE(base::Contains(served_urls, GURL(kURLRegisterStart)));
+    EXPECT_TRUE(base::Contains(served_urls, GURL(kURLRegisterClaimToken)));
     test_url_loader_factory()->SetInterceptor(base::NullCallback());
   }
 
@@ -523,10 +523,10 @@
             run_loop.Quit();
         }));
     run_loop.Run();
-    EXPECT_TRUE(base::ContainsKey(served_urls, GURL(kURLRegisterClaimToken)));
-    EXPECT_TRUE(base::ContainsKey(served_urls, GURL(kURLCloudPrintConfirm)));
-    EXPECT_TRUE(base::ContainsKey(served_urls, GURL(kURLRegisterComplete)));
-    EXPECT_TRUE(base::ContainsKey(served_urls, GURL(kURLInfo)));
+    EXPECT_TRUE(base::Contains(served_urls, GURL(kURLRegisterClaimToken)));
+    EXPECT_TRUE(base::Contains(served_urls, GURL(kURLCloudPrintConfirm)));
+    EXPECT_TRUE(base::Contains(served_urls, GURL(kURLRegisterComplete)));
+    EXPECT_TRUE(base::Contains(served_urls, GURL(kURLInfo)));
     test_url_loader_factory()->SetInterceptor(base::NullCallback());
   }
 
diff --git a/chrome/browser/ui/webui/local_discovery/local_discovery_ui_handler.cc b/chrome/browser/ui/webui/local_discovery/local_discovery_ui_handler.cc
index ae9fae4..bc4fe32 100644
--- a/chrome/browser/ui/webui/local_discovery/local_discovery_ui_handler.cc
+++ b/chrome/browser/ui/webui/local_discovery/local_discovery_ui_handler.cc
@@ -79,13 +79,13 @@
                      const std::set<std::string>& local_ids,
                      base::ListValue* devices_list) {
   for (const auto& i : devices) {
-    if (base::ContainsKey(local_ids, i.id)) {
+    if (base::Contains(local_ids, i.id)) {
       devices_list->Append(CreateDeviceInfo(i));
     }
   }
 
   for (const auto& i : devices) {
-    if (!base::ContainsKey(local_ids, i.id)) {
+    if (!base::Contains(local_ids, i.id)) {
       devices_list->Append(CreateDeviceInfo(i));
     }
   }
@@ -300,8 +300,7 @@
     const GURL& url) {
   web_ui()->CallJavascriptFunctionUnsafe(
       "local_discovery.onRegistrationConfirmedOnPrinter");
-  if (!base::ContainsKey(device_descriptions_,
-                         current_http_client_->GetName())) {
+  if (!base::Contains(device_descriptions_, current_http_client_->GetName())) {
     SendRegisterError();
     return;
   }
diff --git a/chrome/browser/ui/webui/media_router/media_router_ui.cc b/chrome/browser/ui/webui/media_router/media_router_ui.cc
index 11d65b67..04bf7f8c 100644
--- a/chrome/browser/ui/webui/media_router/media_router_ui.cc
+++ b/chrome/browser/ui/webui/media_router/media_router_ui.cc
@@ -292,7 +292,7 @@
 
   for (const MediaRoute& route : routes) {
     if (route.for_display() &&
-        base::ContainsValue(joinable_route_ids, route.media_route_id())) {
+        base::Contains(joinable_route_ids, route.media_route_id())) {
       joinable_route_ids_.push_back(route.media_route_id());
     }
   }
diff --git a/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.cc b/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.cc
index b6b9fe2e..6ec7418 100644
--- a/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.cc
+++ b/chrome/browser/ui/webui/media_router/media_router_webui_message_handler.cc
@@ -551,7 +551,7 @@
   // that should be the cast mode initially selected in the dialog. Otherwise
   // the initial cast mode should be chosen automatically by the dialog.
   bool use_tab_mirroring =
-      base::ContainsKey(cast_modes, MediaCastMode::TAB_MIRROR) &&
+      base::Contains(cast_modes, MediaCastMode::TAB_MIRROR) &&
       media_router_ui_->UserSelectedTabMirroringForCurrentOrigin();
   initial_data.SetBoolean("useTabMirroring", use_tab_mirroring);
 
@@ -1146,8 +1146,7 @@
   auto value = std::make_unique<base::ListValue>();
 
   for (const MediaRoute& route : routes) {
-    bool can_join =
-        base::ContainsValue(joinable_route_ids, route.media_route_id());
+    bool can_join = base::Contains(joinable_route_ids, route.media_route_id());
     int current_cast_mode =
         CurrentCastModeForRouteId(route.media_route_id(), current_cast_modes);
     std::unique_ptr<base::DictionaryValue> route_val(
diff --git a/chrome/browser/ui/webui/password_manager_internals/password_manager_internals_ui.cc b/chrome/browser/ui/webui/password_manager_internals/password_manager_internals_ui.cc
index 64c6734..8bd114b 100644
--- a/chrome/browser/ui/webui/password_manager_internals/password_manager_internals_ui.cc
+++ b/chrome/browser/ui/webui/password_manager_internals/password_manager_internals_ui.cc
@@ -84,7 +84,7 @@
   if (url.is_valid() && url.has_query()) {
     std::vector<std::string> query_parameters = base::SplitString(
         url.query(), "&", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
-    if (base::ContainsValue(query_parameters, "reset_fre"))
+    if (base::Contains(query_parameters, "reset_fre"))
       ResetAutoSignInFirstRunExperience();
   }
 }
diff --git a/chrome/browser/ui/webui/print_preview/pdf_printer_handler.cc b/chrome/browser/ui/webui/print_preview/pdf_printer_handler.cc
index e0d6a90..f922611 100644
--- a/chrome/browser/ui/webui/print_preview/pdf_printer_handler.cc
+++ b/chrome/browser/ui/webui/print_preview/pdf_printer_handler.cc
@@ -97,7 +97,7 @@
   Media default_media("", "", default_media_size.width(),
                       default_media_size.height());
   if (!default_media.MatchBySize() ||
-      !base::ContainsValue(kPdfMedia, default_media.type)) {
+      !base::Contains(kPdfMedia, default_media.type)) {
     default_media =
         Media(locale == "en-US" ? MediaType::NA_LETTER : MediaType::ISO_A4);
   }
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_handler.cc b/chrome/browser/ui/webui/print_preview/print_preview_handler.cc
index a1ff2e4..260a56b 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_handler.cc
+++ b/chrome/browser/ui/webui/print_preview/print_preview_handler.cc
@@ -600,7 +600,7 @@
     return false;
   }
 
-  if (!base::ContainsKey(preview_callbacks_, request_id)) {
+  if (!base::Contains(preview_callbacks_, request_id)) {
     BadMessageReceived();
     return false;
   }
@@ -703,7 +703,7 @@
   int request_id = settings.FindIntKey(kPreviewRequestID).value();
   CHECK_GT(request_id, -1);
 
-  CHECK(!base::ContainsKey(preview_callbacks_, request_id));
+  CHECK(!base::Contains(preview_callbacks_, request_id));
   preview_callbacks_[request_id] = callback_id;
   print_preview_ui()->OnPrintPreviewRequest(request_id);
   // Add an additional key in order to identify |print_preview_ui| later on
@@ -1208,7 +1208,7 @@
   // gets called, the print preview may have failed. Since the failure message
   // may have arrived first, check for this case and bail out instead of
   // thinking this may be a bad IPC message.
-  if (base::ContainsKey(preview_failures_, preview_request_id))
+  if (base::Contains(preview_failures_, preview_request_id))
     return;
 
   if (!ShouldReceiveRendererMessage(preview_request_id))
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_utils.cc b/chrome/browser/ui/webui/print_preview/print_preview_utils.cc
index b5bbf38..71f576dd 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_utils.cc
+++ b/chrome/browser/ui/webui/print_preview/print_preview_utils.cc
@@ -58,7 +58,7 @@
 
     printer_info->SetBoolean(
         kCUPSEnterprisePrinter,
-        base::ContainsKey(printer.options, kCUPSEnterprisePrinter) &&
+        base::Contains(printer.options, kCUPSEnterprisePrinter) &&
             printer.options.at(kCUPSEnterprisePrinter) == kValueTrue);
 
     printer_info->Set(kSettingPrinterOptions, std::move(options));
diff --git a/chrome/browser/ui/webui/profile_helper_browsertest.cc b/chrome/browser/ui/webui/profile_helper_browsertest.cc
index 935f029..b5c8ba75 100644
--- a/chrome/browser/ui/webui/profile_helper_browsertest.cc
+++ b/chrome/browser/ui/webui/profile_helper_browsertest.cc
@@ -80,7 +80,7 @@
 
   // Sanity checks.
   EXPECT_EQ(1u, browser_list->size());
-  EXPECT_TRUE(base::ContainsValue(*browser_list, original_browser));
+  EXPECT_TRUE(base::Contains(*browser_list, original_browser));
 
   // Opening existing browser profile shouldn't open additional browser windows.
   webui::OpenNewWindowForProfile(original_profile);
@@ -120,7 +120,7 @@
 
   BrowserList* browser_list = BrowserList::GetInstance();
   EXPECT_EQ(1u, browser_list->size());
-  EXPECT_TRUE(base::ContainsValue(*browser_list, original_browser));
+  EXPECT_TRUE(base::Contains(*browser_list, original_browser));
   EXPECT_EQ(1u, storage.GetNumberOfProfiles());
 
   // Original browser will be closed, and browser with the new profile created.
@@ -135,7 +135,7 @@
   close_observer.Wait();
 
   EXPECT_EQ(1u, browser_list->size());
-  EXPECT_FALSE(base::ContainsValue(*browser_list, original_browser));
+  EXPECT_FALSE(base::Contains(*browser_list, original_browser));
   EXPECT_EQ(1u, storage.GetNumberOfProfiles());
 }
 
@@ -147,7 +147,7 @@
 
   BrowserList* browser_list = BrowserList::GetInstance();
   EXPECT_EQ(1u, browser_list->size());
-  EXPECT_TRUE(base::ContainsValue(*browser_list, original_browser));
+  EXPECT_TRUE(base::Contains(*browser_list, original_browser));
   EXPECT_EQ(1u, storage.GetNumberOfProfiles());
 
   Profile* additional_profile = CreateProfile();
@@ -177,7 +177,7 @@
 
   BrowserList* browser_list = BrowserList::GetInstance();
   EXPECT_EQ(1u, browser_list->size());
-  EXPECT_TRUE(base::ContainsValue(*browser_list, original_browser));
+  EXPECT_TRUE(base::Contains(*browser_list, original_browser));
   EXPECT_EQ(1u, storage.GetNumberOfProfiles());
 
   Profile* additional_profile = CreateProfile();
@@ -191,6 +191,6 @@
   inhibitor.ContinueToCompletion();
 
   EXPECT_EQ(1u, browser_list->size());
-  EXPECT_TRUE(base::ContainsValue(*browser_list, original_browser));
+  EXPECT_TRUE(base::Contains(*browser_list, original_browser));
   EXPECT_EQ(1u, storage.GetNumberOfProfiles());
 }
diff --git a/chrome/browser/ui/webui/settings/chromeos/fingerprint_handler.cc b/chrome/browser/ui/webui/settings/chromeos/fingerprint_handler.cc
index 3a383f4..1e875bc 100644
--- a/chrome/browser/ui/webui/settings/chromeos/fingerprint_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/fingerprint_handler.cc
@@ -248,7 +248,7 @@
     std::string fingerprint_name = l10n_util::GetStringFUTF8(
         IDS_SETTINGS_PEOPLE_LOCK_SCREEN_NEW_FINGERPRINT_DEFAULT_NAME,
         base::NumberToString16(i));
-    if (!base::ContainsValue(fingerprints_labels_, fingerprint_name)) {
+    if (!base::Contains(fingerprints_labels_, fingerprint_name)) {
       fp_service_->StartEnrollSession(user_id_, fingerprint_name);
       break;
     }
diff --git a/chrome/browser/ui/webui/settings/site_settings_handler.cc b/chrome/browser/ui/webui/settings/site_settings_handler.cc
index 9a3ca49..36cbda3d 100644
--- a/chrome/browser/ui/webui/settings/site_settings_handler.cc
+++ b/chrome/browser/ui/webui/settings/site_settings_handler.cc
@@ -251,7 +251,7 @@
       origin_object.SetKey(kNumCookies, base::Value(0));
       origin_object.SetKey(
           kHasPermissionSettings,
-          base::Value(base::ContainsKey(origin_permission_set, origin)));
+          base::Value(base::Contains(origin_permission_set, origin)));
       origin_list.GetList().emplace_back(std::move(origin_object));
     }
     site_group.SetKey(kNumCookies, base::Value(0));
diff --git a/chrome/browser/ui/webui/welcome/nux_helper.cc b/chrome/browser/ui/webui/welcome/nux_helper.cc
index e984fb0..13a0118 100644
--- a/chrome/browser/ui/webui/welcome/nux_helper.cc
+++ b/chrome/browser/ui/webui/welcome/nux_helper.cc
@@ -262,7 +262,7 @@
                std::back_inserter(filtered_modules),
                [available_modules](std::string module) {
                  return !module.empty() &&
-                        base::ContainsValue(available_modules, module);
+                        base::Contains(available_modules, module);
                });
 
   return base::JoinString(filtered_modules, ",");
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 00324b0..5d66a48 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -260,6 +260,11 @@
 const base::Feature kDriveFcmInvalidations{"DriveFCMInvalidations",
                                            base::FEATURE_ENABLED_BY_DEFAULT};
 
+// If enabled, policies will use FCM (Firebase Cloud Messaging) for its
+// invalidations.
+const base::Feature kPolicyFcmInvalidations{"PolicyFCMInvalidations",
+                                            base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Show the number of open incognito windows besides incognito icon on the
 // toolbar.
 const base::Feature kEnableIncognitoWindowCounter{
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 19e38e6..77bcd4f 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -158,6 +158,9 @@
 extern const base::Feature kDriveFcmInvalidations;
 
 COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kPolicyFcmInvalidations;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kEnableIncognitoWindowCounter;
 
 #if !defined(OS_ANDROID)
diff --git a/chrome/common/instant_struct_traits.h b/chrome/common/instant_struct_traits.h
index 66cf564..55260ca 100644
--- a/chrome/common/instant_struct_traits.h
+++ b/chrome/common/instant_struct_traits.h
@@ -55,13 +55,6 @@
   IPC_STRUCT_TRAITS_MEMBER(data_generation_time)
 IPC_STRUCT_TRAITS_END()
 
-IPC_STRUCT_TRAITS_BEGIN(RGBAColor)
-  IPC_STRUCT_TRAITS_MEMBER(r)
-  IPC_STRUCT_TRAITS_MEMBER(g)
-  IPC_STRUCT_TRAITS_MEMBER(b)
-  IPC_STRUCT_TRAITS_MEMBER(a)
-IPC_STRUCT_TRAITS_END()
-
 IPC_STRUCT_TRAITS_BEGIN(ThemeBackgroundInfo)
   IPC_STRUCT_TRAITS_MEMBER(using_default_theme)
   IPC_STRUCT_TRAITS_MEMBER(using_dark_mode)
diff --git a/chrome/common/search/instant_types.cc b/chrome/common/search/instant_types.cc
index 658748a..26d9a05 100644
--- a/chrome/common/search/instant_types.cc
+++ b/chrome/common/search/instant_types.cc
@@ -4,26 +4,6 @@
 
 #include "chrome/common/search/instant_types.h"
 
-RGBAColor::RGBAColor()
-    : r(0),
-      g(0),
-      b(0),
-      a(0) {
-}
-
-RGBAColor::RGBAColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
-    : r(r), g(g), b(b), a(a) {}
-
-RGBAColor::~RGBAColor() {
-}
-
-bool RGBAColor::operator==(const RGBAColor& rhs) const {
-  return r == rhs.r &&
-      g == rhs.g &&
-      b == rhs.b &&
-      a == rhs.a;
-}
-
 ThemeBackgroundInfo::ThemeBackgroundInfo()
     : using_default_theme(true),
       using_dark_mode(false),
diff --git a/chrome/common/search/instant_types.h b/chrome/common/search/instant_types.h
index 8b4e137a..aba03392 100644
--- a/chrome/common/search/instant_types.h
+++ b/chrome/common/search/instant_types.h
@@ -14,6 +14,7 @@
 #include "base/time/time.h"
 #include "components/ntp_tiles/tile_source.h"
 #include "components/ntp_tiles/tile_title_source.h"
+#include "third_party/skia/include/core/SkColor.h"
 #include "url/gurl.h"
 
 // ID used by Instant code to refer to objects (e.g. Autocomplete results, Most
@@ -41,22 +42,6 @@
   THEME_BKGRND_IMAGE_LAST = THEME_BKGRND_IMAGE_REPEAT,
 };
 
-// The RGBA color components for the text and links of the theme.
-struct RGBAColor {
-  RGBAColor();
-  RGBAColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a);
-  ~RGBAColor();
-
-  bool operator==(const RGBAColor& rhs) const;
-
-  // The color in RGBA format where the R, G, B and A values
-  // are between 0 and 255 inclusive and always valid.
-  uint8_t r;
-  uint8_t g;
-  uint8_t b;
-  uint8_t a;
-};
-
 // Theme background settings for the NTP.
 struct ThemeBackgroundInfo {
   ThemeBackgroundInfo();
@@ -82,14 +67,14 @@
   // Url to learn more info about the custom background.
   GURL custom_background_attribution_action_url;
 
-  // The theme background color in RGBA format always valid.
-  RGBAColor background_color;
+  // The theme background color. Always valid.
+  SkColor background_color;
 
-  // The theme text color in RGBA format.
-  RGBAColor text_color;
+  // The theme text color.
+  SkColor text_color;
 
-  // The theme text color light in RGBA format.
-  RGBAColor text_color_light;
+  // The theme text color light.
+  SkColor text_color_light;
 
   // The theme id for the theme background image.
   // Value is only valid if there's a custom theme background image.
diff --git a/chrome/renderer/searchbox/searchbox_extension.cc b/chrome/renderer/searchbox/searchbox_extension.cc
index 1bce89a..abd8880 100644
--- a/chrome/renderer/searchbox/searchbox_extension.cc
+++ b/chrome/renderer/searchbox/searchbox_extension.cc
@@ -53,23 +53,6 @@
 #include "v8/include/v8.h"
 
 namespace internal {  // for testing.
-
-// Returns an array with the RGBA color components.
-v8::Local<v8::Value> RGBAColorToArray(v8::Isolate* isolate,
-                                      const RGBAColor& color) {
-  v8::Local<v8::Context> context = isolate->GetCurrentContext();
-  v8::Local<v8::Array> color_array = v8::Array::New(isolate, 4);
-  color_array->CreateDataProperty(context, 0, v8::Int32::New(isolate, color.r))
-      .Check();
-  color_array->CreateDataProperty(context, 1, v8::Int32::New(isolate, color.g))
-      .Check();
-  color_array->CreateDataProperty(context, 2, v8::Int32::New(isolate, color.b))
-      .Check();
-  color_array->CreateDataProperty(context, 3, v8::Int32::New(isolate, color.a))
-      .Check();
-  return color_array;
-}
-
 // Whether NTP background should be considered dark, so the colors of various
 // UI elements can be adjusted. Light text implies dark theme.
 bool IsNtpBackgroundDark(SkColor ntp_text) {
@@ -97,13 +80,6 @@
   return bg_color;
 }
 
-// TODO(gayane): Consider removing RGBAColor struct and replacing it with
-// SkColor.
-// Converts RGBAColor to SkColor.
-SkColor RGBAColorToSkColor(const RGBAColor& color) {
-  return SkColorSetARGB(color.a, color.r, color.g, color.b);
-}
-
 // Use dark icon when in dark mode and no background. Otherwise, use
 // light icon for NTPs with images, and themed icon for NTPs with solid color.
 SkColor GetIconColor(const ThemeBackgroundInfo& theme_info) {
@@ -116,7 +92,7 @@
   if (theme_info.using_dark_mode && theme_info.using_default_theme)
     return gfx::kGoogleGrey900;
 
-  SkColor bg_color = RGBAColorToSkColor(theme_info.background_color);
+  SkColor bg_color = theme_info.background_color;
   SkColor icon_color = gfx::kGoogleGrey100;
   if (!theme_info.using_default_theme && bg_color != SK_ColorWHITE)
     icon_color = CalculateIconColor(bg_color);
@@ -248,14 +224,28 @@
   return maybe_int.ToLocalChecked()->Value();
 }
 
-// Converts SkColor to RGBAColor
-RGBAColor SkColorToRGBAColor(const SkColor& sKColor) {
-  RGBAColor color;
-  color.r = SkColorGetR(sKColor);
-  color.g = SkColorGetG(sKColor);
-  color.b = SkColorGetB(sKColor);
-  color.a = SkColorGetA(sKColor);
-  return color;
+// Returns an array with the RGBA color components.
+v8::Local<v8::Value> SkColorToArray(v8::Isolate* isolate,
+                                    const SkColor& color) {
+  v8::Local<v8::Context> context = isolate->GetCurrentContext();
+  v8::Local<v8::Array> color_array = v8::Array::New(isolate, 4);
+  color_array
+      ->CreateDataProperty(context, 0,
+                           v8::Int32::New(isolate, SkColorGetR(color)))
+      .Check();
+  color_array
+      ->CreateDataProperty(context, 1,
+                           v8::Int32::New(isolate, SkColorGetG(color)))
+      .Check();
+  color_array
+      ->CreateDataProperty(context, 2,
+                           v8::Int32::New(isolate, SkColorGetB(color)))
+      .Check();
+  color_array
+      ->CreateDataProperty(context, 3,
+                           v8::Int32::New(isolate, SkColorGetA(color)))
+      .Check();
+  return color_array;
 }
 
 v8::Local<v8::Object> GenerateThemeBackgroundInfo(
@@ -275,12 +265,12 @@
   // Theme color for background as an array with the RGBA components in order.
   // Value is always valid.
   builder.Set("backgroundColorRgba",
-              internal::RGBAColorToArray(isolate, theme_info.background_color));
+              SkColorToArray(isolate, theme_info.background_color));
 
   // Theme color for light text as an array with the RGBA components in order.
   // Value is always valid.
   builder.Set("textColorLightRgba",
-              internal::RGBAColorToArray(isolate, theme_info.text_color_light));
+              SkColorToArray(isolate, theme_info.text_color_light));
 
   // The theme alternate logo value indicates a white logo when TRUE and a
   // colorful one when FALSE.
@@ -357,12 +347,12 @@
   // Assume that a custom background has not been configured and then
   // override based on the condition below.
   builder.Set("customBackgroundConfigured", false);
-  RGBAColor ntp_text = theme_info.text_color;
+  SkColor ntp_text = theme_info.text_color;
 
   // If a custom background has been set provide the relevant information to the
   // page.
   if (!theme_info.custom_background_url.is_empty()) {
-    ntp_text = RGBAColor{248, 249, 250, 255};  // GG050
+    ntp_text = SkColorSetARGB(255, 248, 249, 250);  // GG050
     builder.Set("alternateLogo", true);
     builder.Set("customBackgroundConfigured", true);
     builder.Set("imageUrl", theme_info.custom_background_url.spec());
@@ -379,19 +369,15 @@
 
   // Theme color for text as an array with the RGBA components in order.
   // Value is always valid.
-  builder.Set("textColorRgba", internal::RGBAColorToArray(isolate, ntp_text));
+  builder.Set("textColorRgba", SkColorToArray(isolate, ntp_text));
 
   // Generate fields for themeing NTP elements.
-  builder.Set(
-      "isNtpBackgroundDark",
-      internal::IsNtpBackgroundDark(internal::RGBAColorToSkColor(ntp_text)));
+  builder.Set("isNtpBackgroundDark", internal::IsNtpBackgroundDark(ntp_text));
   builder.Set("useTitleContainer",
               crx_file::id_util::IdIsValid(theme_info.theme_id));
 
   SkColor icon_color = internal::GetIconColor(theme_info);
-  builder.Set(
-      "iconBackgroundColor",
-      internal::RGBAColorToArray(isolate, SkColorToRGBAColor(icon_color)));
+  builder.Set("iconBackgroundColor", SkColorToArray(isolate, icon_color));
   builder.Set("useWhiteAddIcon", color_utils::IsDark(icon_color));
 
   return builder.Build();
diff --git a/chrome/renderer/searchbox/searchbox_extension_unittest.cc b/chrome/renderer/searchbox/searchbox_extension_unittest.cc
index bec21b97..4317167 100644
--- a/chrome/renderer/searchbox/searchbox_extension_unittest.cc
+++ b/chrome/renderer/searchbox/searchbox_extension_unittest.cc
@@ -55,7 +55,7 @@
   ThemeBackgroundInfo theme_info;
   theme_info.using_default_theme = true;
   theme_info.using_dark_mode = false;
-  theme_info.background_color = RGBAColor(255, 0, 0, 255);  // red
+  theme_info.background_color = SK_ColorRED;
 
   // // Default theme in light mode.
   EXPECT_EQ(kLightIconColor, GetIconColor(theme_info));
diff --git a/chrome/service/cloud_print/cloud_print_connector.cc b/chrome/service/cloud_print/cloud_print_connector.cc
index 71cf5cf..b03bac3 100644
--- a/chrome/service/cloud_print/cloud_print_connector.cc
+++ b/chrome/service/cloud_print/cloud_print_connector.cc
@@ -574,8 +574,8 @@
   // continue in OnReceivePrinterCaps.
   print_system_->GetPrinterCapsAndDefaults(
       info.printer_name.c_str(),
-      base::Bind(&CloudPrintConnector::OnReceivePrinterCaps,
-                 base::Unretained(this)));
+      base::BindOnce(&CloudPrintConnector::OnReceivePrinterCaps,
+                     base::Unretained(this)));
 }
 
 void CloudPrintConnector::OnPrinterDelete(const std::string& printer_id) {
diff --git a/chrome/service/cloud_print/cloud_print_proxy_backend.cc b/chrome/service/cloud_print/cloud_print_proxy_backend.cc
index 51b9079..05c1ed7 100644
--- a/chrome/service/cloud_print/cloud_print_proxy_backend.cc
+++ b/chrome/service/cloud_print/cloud_print_proxy_backend.cc
@@ -129,7 +129,7 @@
   CloudPrintProxyFrontend* frontend() { return backend_->frontend_; }
 
   bool PostFrontendTask(const base::Location& from_here,
-                        const base::Closure& task);
+                        base::OnceClosure task);
 
   bool CurrentlyOnFrontendThread() const;
   bool CurrentlyOnCoreThread() const;
@@ -240,9 +240,10 @@
     const std::string& cloud_print_token) {
   if (!core_thread_.Start())
     return false;
-  PostCoreTask(FROM_HERE,
-               base::Bind(&CloudPrintProxyBackend::Core::DoInitializeWithToken,
-                          core_, cloud_print_token));
+  PostCoreTask(
+      FROM_HERE,
+      base::BindOnce(&CloudPrintProxyBackend::Core::DoInitializeWithToken,
+                     core_, cloud_print_token));
   return true;
 }
 
@@ -253,8 +254,8 @@
     return false;
   PostCoreTask(
       FROM_HERE,
-      base::Bind(&CloudPrintProxyBackend::Core::DoInitializeWithRobotToken,
-                 core_, robot_oauth_refresh_token, robot_email));
+      base::BindOnce(&CloudPrintProxyBackend::Core::DoInitializeWithRobotToken,
+                     core_, robot_oauth_refresh_token, robot_email));
   return true;
 }
 
@@ -263,29 +264,30 @@
     const std::string& robot_email) {
   if (!core_thread_.Start())
     return false;
-  PostCoreTask(
-      FROM_HERE,
-      base::Bind(&CloudPrintProxyBackend::Core::DoInitializeWithRobotAuthCode,
-                 core_, robot_oauth_auth_code, robot_email));
+  PostCoreTask(FROM_HERE,
+               base::BindOnce(
+                   &CloudPrintProxyBackend::Core::DoInitializeWithRobotAuthCode,
+                   core_, robot_oauth_auth_code, robot_email));
   return true;
 }
 
 void CloudPrintProxyBackend::Shutdown() {
-  PostCoreTask(FROM_HERE, base::Bind(&CloudPrintProxyBackend::Core::DoShutdown,
-                                     core_));
+  PostCoreTask(
+      FROM_HERE,
+      base::BindOnce(&CloudPrintProxyBackend::Core::DoShutdown, core_));
   core_thread_.Stop();
   core_ = nullptr;  // Releases reference to |core_|.
 }
 
 void CloudPrintProxyBackend::UnregisterPrinters() {
   PostCoreTask(FROM_HERE,
-               base::Bind(&CloudPrintProxyBackend::Core::DoUnregisterPrinters,
-                          core_));
+               base::BindOnce(
+                   &CloudPrintProxyBackend::Core::DoUnregisterPrinters, core_));
 }
 
 bool CloudPrintProxyBackend::PostCoreTask(const base::Location& from_here,
-                                          const base::Closure& task) {
-  return core_thread_.task_runner()->PostTask(from_here, task);
+                                          base::OnceClosure task) {
+  return core_thread_.task_runner()->PostTask(from_here, std::move(task));
 }
 
 CloudPrintProxyBackend::Core::Core(
@@ -308,8 +310,8 @@
 
 bool CloudPrintProxyBackend::Core::PostFrontendTask(
     const base::Location& from_here,
-    const base::Closure& task) {
-  return backend_->frontend_task_runner_->PostTask(from_here, task);
+    base::OnceClosure task) {
+  return backend_->frontend_task_runner_->PostTask(from_here, std::move(task));
 }
 
 bool CloudPrintProxyBackend::Core::CurrentlyOnFrontendThread() const {
@@ -380,9 +382,9 @@
   token_store->SetToken(access_token);
   robot_email_ = robot_email;
   // Let the frontend know that we have authenticated.
-  PostFrontendTask(FROM_HERE, base::Bind(&Core::NotifyAuthenticated, this,
-                                         robot_oauth_refresh_token, robot_email,
-                                         user_email));
+  PostFrontendTask(FROM_HERE, base::BindOnce(&Core::NotifyAuthenticated, this,
+                                             robot_oauth_refresh_token,
+                                             robot_email, user_email));
   if (first_time) {
     InitNotifications(robot_email, access_token);
   } else {
@@ -397,8 +399,8 @@
   if (!connector_->IsRunning()) {
     if (!connector_->Start()) {
       // Let the frontend know that we do not have a print system.
-      PostFrontendTask(FROM_HERE,
-                       base::Bind(&Core::NotifyPrintSystemUnavailable, this));
+      PostFrontendTask(
+          FROM_HERE, base::BindOnce(&Core::NotifyPrintSystemUnavailable, this));
     }
   }
 }
@@ -407,7 +409,7 @@
   DCHECK(CurrentlyOnCoreThread());
   VLOG(1) << "CP_CONNECTOR: Auth Error";
   PostFrontendTask(FROM_HERE,
-                   base::Bind(&Core::NotifyAuthenticationFailed, this));
+                   base::BindOnce(&Core::NotifyAuthenticationFailed, this));
 }
 
 scoped_refptr<network::SharedURLLoaderFactory>
@@ -435,8 +437,8 @@
 
 void CloudPrintProxyBackend::Core::OnXmppPingUpdated(int ping_timeout) {
   settings_.SetXmppPingTimeoutSec(ping_timeout);
-  PostFrontendTask(
-      FROM_HERE, base::Bind(&Core::NotifyXmppPingUpdated, this, ping_timeout));
+  PostFrontendTask(FROM_HERE, base::BindOnce(&Core::NotifyXmppPingUpdated, this,
+                                             ping_timeout));
 }
 
 void CloudPrintProxyBackend::Core::InitNotifications(
@@ -497,8 +499,8 @@
 
   std::string access_token = GetTokenStore()->token();
   std::list<std::string> printer_ids = connector_->GetPrinterIds();
-  PostFrontendTask(FROM_HERE, base::Bind(&Core::NotifyUnregisterPrinters, this,
-                                         access_token, printer_ids));
+  PostFrontendTask(FROM_HERE, base::BindOnce(&Core::NotifyUnregisterPrinters,
+                                             this, access_token, printer_ids));
 }
 
 void CloudPrintProxyBackend::Core::HandlePrinterNotification(
diff --git a/chrome/service/cloud_print/cloud_print_proxy_backend.h b/chrome/service/cloud_print/cloud_print_proxy_backend.h
index 845d5b65..4cc863a 100644
--- a/chrome/service/cloud_print/cloud_print_proxy_backend.h
+++ b/chrome/service/cloud_print/cloud_print_proxy_backend.h
@@ -82,7 +82,7 @@
   void UnregisterPrinters();
 
  private:
-  bool PostCoreTask(const base::Location& from_here, const base::Closure& task);
+  bool PostCoreTask(const base::Location& from_here, base::OnceClosure task);
 
   // The real guts of CloudPrintProxyBackend, to keep the public client API
   // clean.
diff --git a/chrome/service/cloud_print/print_system.h b/chrome/service/cloud_print/print_system.h
index 6e06bae1..33119bf 100644
--- a/chrome/service/cloud_print/print_system.h
+++ b/chrome/service/cloud_print/print_system.h
@@ -156,10 +156,8 @@
     std::string message_;
   };
 
-  typedef base::Callback<void(bool,
-                              const std::string&,
-                              const printing::PrinterCapsAndDefaults&)>
-      PrinterCapsAndDefaultsCallback;
+  using PrinterCapsAndDefaultsCallback = base::OnceCallback<
+      void(bool, const std::string&, const printing::PrinterCapsAndDefaults&)>;
 
   // Initialize print system. This need to be called before any other function
   // of PrintSystem.
@@ -172,7 +170,7 @@
   // Gets the capabilities and defaults for a specific printer asynchronously.
   virtual void GetPrinterCapsAndDefaults(
       const std::string& printer_name,
-      const PrinterCapsAndDefaultsCallback& callback) = 0;
+      PrinterCapsAndDefaultsCallback callback) = 0;
 
   // Returns true if printer_name points to a valid printer.
   virtual bool IsValidPrinter(const std::string& printer_name) = 0;
diff --git a/chrome/service/cloud_print/print_system_cups.cc b/chrome/service/cloud_print/print_system_cups.cc
index d38f5b1..a4d2bee 100644
--- a/chrome/service/cloud_print/print_system_cups.cc
+++ b/chrome/service/cloud_print/print_system_cups.cc
@@ -86,7 +86,7 @@
       printing::PrinterList* printer_list) override;
   void GetPrinterCapsAndDefaults(
       const std::string& printer_name,
-      const PrinterCapsAndDefaultsCallback& callback) override;
+      PrinterCapsAndDefaultsCallback callback) override;
   bool IsValidPrinter(const std::string& printer_name) override;
   bool ValidatePrintTicket(const std::string& printer_name,
                            const std::string& print_ticket_data,
@@ -164,7 +164,7 @@
 
   // Helper method to invoke a PrinterCapsAndDefaultsCallback.
   static void RunCapsCallback(
-      const PrinterCapsAndDefaultsCallback& callback,
+      PrinterCapsAndDefaultsCallback callback,
       bool succeeded,
       const std::string& printer_name,
       const printing::PrinterCapsAndDefaults& printer_info);
@@ -512,12 +512,13 @@
 
 void PrintSystemCUPS::GetPrinterCapsAndDefaults(
     const std::string& printer_name,
-    const PrinterCapsAndDefaultsCallback& callback) {
+    PrinterCapsAndDefaultsCallback callback) {
   printing::PrinterCapsAndDefaults printer_info;
   bool succeeded = GetPrinterCapsAndDefaults(printer_name, &printer_info);
   base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(&PrintSystemCUPS::RunCapsCallback, callback,
-                                succeeded, printer_name, printer_info));
+      FROM_HERE,
+      base::BindOnce(&PrintSystemCUPS::RunCapsCallback, std::move(callback),
+                     succeeded, printer_name, printer_info));
 }
 
 bool PrintSystemCUPS::IsValidPrinter(const std::string& printer_name) {
@@ -852,11 +853,11 @@
 }
 
 void PrintSystemCUPS::RunCapsCallback(
-    const PrinterCapsAndDefaultsCallback& callback,
+    PrinterCapsAndDefaultsCallback callback,
     bool succeeded,
     const std::string& printer_name,
     const printing::PrinterCapsAndDefaults& printer_info) {
-  callback.Run(succeeded, printer_name, printer_info);
+  std::move(callback).Run(succeeded, printer_name, printer_info);
 }
 
 }  // namespace cloud_print
diff --git a/chrome/service/cloud_print/print_system_win.cc b/chrome/service/cloud_print/print_system_win.cc
index 580982c..3274b13 100644
--- a/chrome/service/cloud_print/print_system_win.cc
+++ b/chrome/service/cloud_print/print_system_win.cc
@@ -538,11 +538,9 @@
 // request to fetch printer capabilities and defaults.
 class PrinterCapsHandler : public ServiceUtilityProcessHost::Client {
  public:
-  PrinterCapsHandler(
-      const std::string& printer_name,
-      const PrintSystem::PrinterCapsAndDefaultsCallback& callback)
-          : printer_name_(printer_name), callback_(callback) {
-  }
+  PrinterCapsHandler(const std::string& printer_name,
+                     PrintSystem::PrinterCapsAndDefaultsCallback callback)
+      : printer_name_(printer_name), callback_(std::move(callback)) {}
 
   // ServiceUtilityProcessHost::Client implementation.
   void OnChildDied() override {
@@ -554,8 +552,7 @@
       bool succeeded,
       const std::string& printer_name,
       const printing::PrinterCapsAndDefaults& caps_and_defaults) override {
-    callback_.Run(succeeded, printer_name, caps_and_defaults);
-    callback_.Reset();
+    std::move(callback_).Run(succeeded, printer_name, caps_and_defaults);
     Release();
   }
 
@@ -571,8 +568,7 @@
           base::JSONWriter::OPTIONS_PRETTY_PRINT,
           &printer_info.printer_capabilities);
     }
-    callback_.Run(succeeded, printer_name, printer_info);
-    callback_.Reset();
+    std::move(callback_).Run(succeeded, printer_name, printer_info);
     Release();
   }
 
@@ -595,7 +591,7 @@
   ~PrinterCapsHandler() override {}
 
   void GetPrinterCapsAndDefaultsImpl(
-      const scoped_refptr<base::SingleThreadTaskRunner>& client_task_runner) {
+      scoped_refptr<base::SingleThreadTaskRunner> client_task_runner) {
     DCHECK(CurrentlyOnServiceIOThread());
     auto utility_host = std::make_unique<ServiceUtilityProcessHost>(
         this, client_task_runner.get());
@@ -609,7 +605,7 @@
   }
 
   void GetPrinterSemanticCapsAndDefaultsImpl(
-      const scoped_refptr<base::SingleThreadTaskRunner>& client_task_runner) {
+      scoped_refptr<base::SingleThreadTaskRunner> client_task_runner) {
     DCHECK(CurrentlyOnServiceIOThread());
     auto utility_host = std::make_unique<ServiceUtilityProcessHost>(
         this, client_task_runner.get());
@@ -636,7 +632,7 @@
       printing::PrinterList* printer_list) override;
   void GetPrinterCapsAndDefaults(
       const std::string& printer_name,
-      const PrinterCapsAndDefaultsCallback& callback) override;
+      PrinterCapsAndDefaultsCallback callback) override;
   bool IsValidPrinter(const std::string& printer_name) override;
   bool ValidatePrintTicket(
       const std::string& printer_name,
@@ -692,11 +688,12 @@
 
 void PrintSystemWin::GetPrinterCapsAndDefaults(
     const std::string& printer_name,
-    const PrinterCapsAndDefaultsCallback& callback) {
+    PrinterCapsAndDefaultsCallback callback) {
   // Launch as child process to retrieve the capabilities and defaults because
   // this involves invoking a printer driver DLL and crashes have been known to
   // occur.
-  PrinterCapsHandler* handler = new PrinterCapsHandler(printer_name, callback);
+  PrinterCapsHandler* handler =
+      new PrinterCapsHandler(printer_name, std::move(callback));
   handler->AddRef();
   if (use_cdd_)
     handler->StartGetPrinterSemanticCapsAndDefaults();
diff --git a/chrome/service/cloud_print/printer_job_handler.cc b/chrome/service/cloud_print/printer_job_handler.cc
index adae1ca..cebf58c08 100644
--- a/chrome/service/cloud_print/printer_job_handler.cc
+++ b/chrome/service/cloud_print/printer_job_handler.cc
@@ -652,8 +652,8 @@
   // continue in OnReceivePrinterCaps.
   print_system_->GetPrinterCapsAndDefaults(
       printer_info.printer_name,
-      base::Bind(&PrinterJobHandler::OnReceivePrinterCaps,
-                 weak_ptr_factory_.GetWeakPtr()));
+      base::BindOnce(&PrinterJobHandler::OnReceivePrinterCaps,
+                     weak_ptr_factory_.GetWeakPtr()));
 
   // While we are waiting for the data, pretend we have work to do and return
   // true.
diff --git a/chrome/service/cloud_print/printer_job_handler_unittest.cc b/chrome/service/cloud_print/printer_job_handler_unittest.cc
index 814e686..15160ca 100644
--- a/chrome/service/cloud_print/printer_job_handler_unittest.cc
+++ b/chrome/service/cloud_print/printer_job_handler_unittest.cc
@@ -410,10 +410,9 @@
   MOCK_METHOD1(EnumeratePrinters, PrintSystem::PrintSystemResult(
       printing::PrinterList* printer_list));
 
-  MOCK_METHOD2(
-      GetPrinterCapsAndDefaults,
-      void(const std::string& printer_name,
-           const PrintSystem::PrinterCapsAndDefaultsCallback& callback));
+  MOCK_METHOD2(GetPrinterCapsAndDefaults,
+               void(const std::string& printer_name,
+                    PrintSystem::PrinterCapsAndDefaultsCallback callback));
 
   MOCK_METHOD1(IsValidPrinter, bool(const std::string& printer_name));
 
@@ -456,7 +455,7 @@
   bool GetPrinterInfo(printing::PrinterBasicInfo* info);
   void SendCapsAndDefaults(
       const std::string& printer_name,
-      const PrintSystem::PrinterCapsAndDefaultsCallback& callback);
+      PrintSystem::PrinterCapsAndDefaultsCallback callback);
   void AddMimeHeader(const GURL& url, net::FakeURLFetcher* fetcher);
   void AddTicketMimeHeader(const GURL& url, net::FakeURLFetcher* fetcher);
   bool PostSpoolSuccess();
@@ -522,9 +521,9 @@
 }
 
 PrinterJobHandlerTest::PrinterJobHandlerTest()
-    : factory_(NULL, base::Bind(&TestURLFetcherCallback::CreateURLFetcher,
-                                base::Unretained(&url_callback_))) {
-}
+    : factory_(nullptr,
+               base::BindRepeating(&TestURLFetcherCallback::CreateURLFetcher,
+                                   base::Unretained(&url_callback_))) {}
 
 bool PrinterJobHandlerTest::PostSpoolSuccess() {
   base::ThreadTaskRunnerHandle::Get()->PostTask(
@@ -614,8 +613,8 @@
 
 void PrinterJobHandlerTest::SendCapsAndDefaults(
     const std::string& printer_name,
-    const PrintSystem::PrinterCapsAndDefaultsCallback& callback) {
-  callback.Run(true, printer_name, caps_and_defaults_);
+    PrintSystem::PrinterCapsAndDefaultsCallback callback) {
+  std::move(callback).Run(true, printer_name, caps_and_defaults_);
 }
 
 bool PrinterJobHandlerTest::GetPrinterInfo(printing::PrinterBasicInfo* info) {
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 2704474..ccc40be 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3051,6 +3051,7 @@
     "../browser/sessions/chrome_serialized_navigation_driver_unittest.cc",
     "../browser/sessions/restore_on_startup_policy_handler_unittest.cc",
     "../browser/sessions/session_common_utils_unittest.cc",
+    "../browser/sharing/sharing_sync_preference_unittest.cc",
     "../browser/shell_integration_win_unittest.cc",
     "../browser/signin/account_consistency_mode_manager_unittest.cc",
     "../browser/signin/chrome_device_id_helper_unittest.cc",
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index 45f8c1b5..07833926 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -3103,18 +3103,6 @@
     ]
   },
 
-  "DockedMagnifierEnabled": {
-    "os": ["chromeos"],
-    "test_policy": { "DockedMagnifierEnabled": true },
-    "pref_mappings": [
-      { "pref": "ash.docked_magnifier.enabled",
-        "indicator_tests": [
-          { "policy": { "DockedMagnifierEnabled": true } }
-        ]
-      }
-    ]
-  },
-
   "SpokenFeedbackEnabled": {
     "os": ["chromeos"],
     "test_policy": { "SpokenFeedbackEnabled": true },
@@ -3148,7 +3136,8 @@
           { "policy": { "ScreenMagnifierType": 1 } }
         ]
       },
-      { "pref": "settings.a11y.screen_magnifier" }
+      { "pref": "settings.a11y.screen_magnifier" },
+      { "pref": "ash.docked_magnifier.enabled" }
     ]
   },
 
diff --git a/chromeos/chromeos_strings.grd b/chromeos/chromeos_strings.grd
index 13f7dff8..9cf8952 100644
--- a/chromeos/chromeos_strings.grd
+++ b/chromeos/chromeos_strings.grd
@@ -189,20 +189,20 @@
       </message>
 
       <!-- Password expiry notifications -->
-      <message name="IDS_PASSWORD_HAS_EXPIRED_TITLE" desc="Title for a notification that tells the user their password has expired.">
-        Password has expired
-      </message>
-      <message name="IDS_PASSWORD_WILL_EXPIRE_TITLE" desc="Title for a notification that tells the user their password will soon expire.">
-        Password will soon expire
-      </message>
       <message name="IDS_PASSWORD_EXPIRY_DAYS_BODY" desc="Message body for a notification that tells the user their password will expire in less than some number of days (where 0 days means it has expired).">
         {NUM_DAYS, plural,
          =0 {Your current password has expired!}
          =1 {Your current password will expire in less than one day!}
          other {Your current password will expire in less than # days!}}
       </message>
-      <message name="IDS_PASSWORD_EXPIRY_CHOOSE_NEW_PASSWORD_LINK" desc="Added to the message body on a notification to explain that clicking the notification will open a dialog where a new password can be chosen.">
-        Click here to choose a new password
+      <message name="IDS_PASSWORD_EXPIRY_CALL_TO_ACTION" desc="Message body on a notification that politely requests the user to choose a new password since their old password is expiring soon">
+        Please choose a new password now
+      </message>
+      <message name="IDS_PASSWORD_EXPIRY_CALL_TO_ACTION_CRITICAL" desc="Message body on a notification that *URGENTLY* requests the user to choose a new password since their old password is expiring *VERY* soon">
+        Please choose a new password immediately
+      </message>
+      <message name="IDS_PASSWORD_EXPIRY_CHANGE_PASSWORD_BUTTON" desc="Text on a button that takes the user to page to change their password">
+        Change password
       </message>
 
       <message name="IDS_IME_SERVICE_DISPLAY_NAME" desc="The display name (in the system task manager, etc) of the service process providing the input methods.">
diff --git a/chromeos/chromeos_strings_grd/IDS_PASSWORD_EXPIRY_CALL_TO_ACTION.png.sha1 b/chromeos/chromeos_strings_grd/IDS_PASSWORD_EXPIRY_CALL_TO_ACTION.png.sha1
new file mode 100644
index 0000000..aa7a50f
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_PASSWORD_EXPIRY_CALL_TO_ACTION.png.sha1
@@ -0,0 +1 @@
+84a5296d18c190df2b50e6ce25a54ddbc4c5f9d4
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_PASSWORD_EXPIRY_CALL_TO_ACTION_CRITICAL.png.sha1 b/chromeos/chromeos_strings_grd/IDS_PASSWORD_EXPIRY_CALL_TO_ACTION_CRITICAL.png.sha1
new file mode 100644
index 0000000..f63998d8
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_PASSWORD_EXPIRY_CALL_TO_ACTION_CRITICAL.png.sha1
@@ -0,0 +1 @@
+0372073a130db8f34c558f2215c6832e55810802
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_PASSWORD_EXPIRY_CHANGE_PASSWORD_BUTTON.png.sha1 b/chromeos/chromeos_strings_grd/IDS_PASSWORD_EXPIRY_CHANGE_PASSWORD_BUTTON.png.sha1
new file mode 100644
index 0000000..2e432ea
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_PASSWORD_EXPIRY_CHANGE_PASSWORD_BUTTON.png.sha1
@@ -0,0 +1 @@
+e75101e417eef40b95dcbbbe80135048c41244d8
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_PASSWORD_EXPIRY_CHOOSE_NEW_PASSWORD_LINK.png.sha1 b/chromeos/chromeos_strings_grd/IDS_PASSWORD_EXPIRY_CHOOSE_NEW_PASSWORD_LINK.png.sha1
deleted file mode 100644
index 230f66f..0000000
--- a/chromeos/chromeos_strings_grd/IDS_PASSWORD_EXPIRY_CHOOSE_NEW_PASSWORD_LINK.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-9d1f66e08d707cc3babe1c353f4417b650a71e10
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_PASSWORD_HAS_EXPIRED_TITLE.png.sha1 b/chromeos/chromeos_strings_grd/IDS_PASSWORD_HAS_EXPIRED_TITLE.png.sha1
deleted file mode 100644
index f8ddd69..0000000
--- a/chromeos/chromeos_strings_grd/IDS_PASSWORD_HAS_EXPIRED_TITLE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-e7ea394b117193e4506f11bb87d47e88dad64a97
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_PASSWORD_WILL_EXPIRE_TITLE.png.sha1 b/chromeos/chromeos_strings_grd/IDS_PASSWORD_WILL_EXPIRE_TITLE.png.sha1
deleted file mode 100644
index 6a2d8e0..0000000
--- a/chromeos/chromeos_strings_grd/IDS_PASSWORD_WILL_EXPIRE_TITLE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-54f9f5c62799b5a050152f23982b4eb275ee1e4c
\ No newline at end of file
diff --git a/components/autofill/core/browser/geo/phone_number_i18n.cc b/components/autofill/core/browser/geo/phone_number_i18n.cc
index d0ea183..3853bfe0 100644
--- a/components/autofill/core/browser/geo/phone_number_i18n.cc
+++ b/components/autofill/core/browser/geo/phone_number_i18n.cc
@@ -265,7 +265,13 @@
                        const base::string16& number_b,
                        const std::string& raw_region,
                        const std::string& app_locale) {
-  // TODO(crbug.com/953678): Maybe return true if two empty strings are given.
+  if (number_a.empty() && number_b.empty()) {
+    return true;
+  }
+
+  if (number_a.empty() || number_b.empty()) {
+    return false;
+  }
 
   // Sanitize the provided |raw_region| before trying to use it for parsing.
   const std::string region = SanitizeRegion(raw_region, app_locale);
diff --git a/components/autofill/core/browser/geo/phone_number_i18n_unittest.cc b/components/autofill/core/browser/geo/phone_number_i18n_unittest.cc
index bb6b574..afcf07b 100644
--- a/components/autofill/core/browser/geo/phone_number_i18n_unittest.cc
+++ b/components/autofill/core/browser/geo/phone_number_i18n_unittest.cc
@@ -253,6 +253,14 @@
   // Different numbers don't match.
   EXPECT_FALSE(PhoneNumbersMatch(ASCIIToUTF16("14158889999"),
                                  ASCIIToUTF16("1415888"), "US", "en-US"));
+
+  // Two empty numbers match.
+  EXPECT_TRUE(
+      PhoneNumbersMatch(base::string16(), base::string16(), "US", "en-US"));
+
+  // An empty and a non-empty number do not match.
+  EXPECT_FALSE(PhoneNumbersMatch(base::string16(), ASCIIToUTF16("5088585123"),
+                                 "US", "en-US"));
 }
 
 // Tests that the phone numbers are correctly formatted for the Payment
diff --git a/components/invalidation/impl/fcm_invalidation_service.cc b/components/invalidation/impl/fcm_invalidation_service.cc
index 236ffd49..78e04a86 100644
--- a/components/invalidation/impl/fcm_invalidation_service.cc
+++ b/components/invalidation/impl/fcm_invalidation_service.cc
@@ -94,6 +94,15 @@
   identity_provider_->AddObserver(this);
 }
 
+// static
+void FCMInvalidationService::RegisterPrefs(PrefRegistrySimple* registry) {
+  registry->RegisterStringPref(
+      invalidation::prefs::kFCMInvalidationClientIDCacheDeprecated,
+      /*default_value=*/std::string());
+  registry->RegisterDictionaryPref(
+      invalidation::prefs::kInvalidationClientIDCache);
+}
+
 void FCMInvalidationService::InitForTest(syncer::Invalidator* invalidator) {
   // Here we perform the equivalent of Init() and StartInvalidator(), but with
   // some minor changes to account for the fact that we're injecting the
diff --git a/components/invalidation/impl/fcm_invalidation_service.h b/components/invalidation/impl/fcm_invalidation_service.h
index d08dc45..484a05a 100644
--- a/components/invalidation/impl/fcm_invalidation_service.h
+++ b/components/invalidation/impl/fcm_invalidation_service.h
@@ -22,6 +22,7 @@
 }
 
 class PrefService;
+class PrefRegistrySimple;
 
 namespace syncer {
 class Invalidator;
@@ -50,6 +51,8 @@
 
   void Init();
 
+  static void RegisterPrefs(PrefRegistrySimple* registry);
+
   // InvalidationService implementation.
   // It is an error to have registered handlers when the service is destroyed.
   void RegisterInvalidationHandler(
diff --git a/components/invalidation/impl/invalidator_registrar_with_memory.cc b/components/invalidation/impl/invalidator_registrar_with_memory.cc
index 3354264..2d1b23cf 100644
--- a/components/invalidation/impl/invalidator_registrar_with_memory.cc
+++ b/components/invalidation/impl/invalidator_registrar_with_memory.cc
@@ -49,6 +49,13 @@
   registry->RegisterDictionaryPref(kTopicsToHandler);
 }
 
+// static
+void InvalidatorRegistrarWithMemory::RegisterPrefs(
+    PrefRegistrySimple* registry) {
+  registry->RegisterDictionaryPref(kTopicsToHandlerDeprecated);
+  registry->RegisterDictionaryPref(kTopicsToHandler);
+}
+
 InvalidatorRegistrarWithMemory::InvalidatorRegistrarWithMemory(
     PrefService* local_state,
     const std::string& sender_id,
diff --git a/components/invalidation/impl/invalidator_registrar_with_memory.h b/components/invalidation/impl/invalidator_registrar_with_memory.h
index 4c3c672..61ebbcf8 100644
--- a/components/invalidation/impl/invalidator_registrar_with_memory.h
+++ b/components/invalidation/impl/invalidator_registrar_with_memory.h
@@ -37,7 +37,14 @@
   // It is an error to have registered handlers on destruction.
   ~InvalidatorRegistrarWithMemory();
 
+  // RegisterProfilePrefs and RegisterPrefs register the same prefs, because on
+  // device level (sign in screen, device local account) we spin up separate
+  // InvalidationService and on profile level (when user signed in) we have
+  // another InvalidationService, and we want to keep profile data in an
+  // encrypted area of disk. While device data which is public can be kept in an
+  // unencrypted area.
   static void RegisterProfilePrefs(PrefRegistrySimple* registry);
+  static void RegisterPrefs(PrefRegistrySimple* registry);
 
   // Updates the set of topics associated with |handler|.  |handler| must
   // not be NULL, and must already be registered.  A topic must be registered
diff --git a/components/invalidation/impl/per_user_topic_registration_manager.cc b/components/invalidation/impl/per_user_topic_registration_manager.cc
index 5fce914..308254f3 100644
--- a/components/invalidation/impl/per_user_topic_registration_manager.cc
+++ b/components/invalidation/impl/per_user_topic_registration_manager.cc
@@ -139,6 +139,17 @@
   registry->RegisterDictionaryPref(kActiveRegistrationTokens);
 }
 
+// static
+void PerUserTopicRegistrationManager::RegisterPrefs(
+    PrefRegistrySimple* registry) {
+  registry->RegisterDictionaryPref(kTypeRegisteredForInvalidationsDeprecated);
+  registry->RegisterStringPref(kActiveRegistrationTokenDeprecated,
+                               std::string());
+
+  registry->RegisterDictionaryPref(kTypeRegisteredForInvalidations);
+  registry->RegisterDictionaryPref(kActiveRegistrationTokens);
+}
+
 struct PerUserTopicRegistrationManager::RegistrationEntry {
   RegistrationEntry(const Topic& id,
                     SubscriptionFinishedCallback completion_callback,
diff --git a/components/invalidation/impl/per_user_topic_registration_manager.h b/components/invalidation/impl/per_user_topic_registration_manager.h
index a47158e..2dbac6c 100644
--- a/components/invalidation/impl/per_user_topic_registration_manager.h
+++ b/components/invalidation/impl/per_user_topic_registration_manager.h
@@ -60,7 +60,14 @@
 
   virtual ~PerUserTopicRegistrationManager();
 
+  // RegisterProfilePrefs and RegisterPrefs register the same prefs, because on
+  // device level (sign in screen, device local account) we spin up separate
+  // InvalidationService and on profile level (when user signed in) we have
+  // another InvalidationService, and we want to keep profile data in an
+  // encrypted area of disk. While device data which is public can be kept in an
+  // unencrypted area.
   static void RegisterProfilePrefs(PrefRegistrySimple* registry);
+  static void RegisterPrefs(PrefRegistrySimple* registry);
 
   virtual void UpdateRegisteredTopics(const Topics& ids,
                                       const std::string& token);
diff --git a/components/invalidation/public/invalidation_util.cc b/components/invalidation/public/invalidation_util.cc
index 4bcfbc83..04ce11d 100644
--- a/components/invalidation/public/invalidation_util.cc
+++ b/components/invalidation/public/invalidation_util.cc
@@ -22,12 +22,12 @@
 const char kSourceKey[] = "source";
 const char kNameKey[] = "name";
 
-const int kDeprecatedSource = 2000;
-
 }  // namespace
 
 namespace syncer {
 
+const int kDeprecatedSourceForFCM = 2000;
+
 bool ObjectIdLessThan::operator()(const invalidation::ObjectId& lhs,
                                   const invalidation::ObjectId& rhs) const {
   return (lhs.source() < rhs.source()) ||
@@ -153,19 +153,19 @@
 ObjectIdSet ConvertTopicsToIds(TopicSet topics) {
   ObjectIdSet ids;
   for (const auto& topic : topics)
-    ids.insert(invalidation::ObjectId(kDeprecatedSource, topic));
+    ids.insert(invalidation::ObjectId(kDeprecatedSourceForFCM, topic));
   return ids;
 }
 
 ObjectIdSet ConvertTopicsToIds(Topics topics) {
   ObjectIdSet ids;
   for (const auto& topic : topics)
-    ids.insert(invalidation::ObjectId(kDeprecatedSource, topic.first));
+    ids.insert(invalidation::ObjectId(kDeprecatedSourceForFCM, topic.first));
   return ids;
 }
 
 invalidation::ObjectId ConvertTopicToId(const Topic& topic) {
-  return invalidation::ObjectId(kDeprecatedSource, topic);
+  return invalidation::ObjectId(kDeprecatedSourceForFCM, topic);
 }
 
 HandlerOwnerType OwnerNameToHandlerType(const std::string& owner_name) {
diff --git a/components/invalidation/public/invalidation_util.h b/components/invalidation/public/invalidation_util.h
index b50abfb3..6baa7b6 100644
--- a/components/invalidation/public/invalidation_util.h
+++ b/components/invalidation/public/invalidation_util.h
@@ -28,6 +28,14 @@
 
 namespace syncer {
 
+// FCMInvalidationService and deprecated TiclInvalidationService uses ObjectId
+// to keep track of objects to invalidate. There are 2 fields in ObjectId:
+// source and name. TiclInvalidationService expects both of them, while
+// FCMInvalidationService only works with the name. So InvalidationService
+// assigns the value of source to kDeprecatedSourceForFCM when FCM (Firebase
+// Cloud Messaging) is enabled.
+extern const int kDeprecatedSourceForFCM;
+
 // Used by UMA histogram, so entries shouldn't be reordered or removed.
 enum class HandlerOwnerType {
   kCloud = 0,
diff --git a/components/password_manager/core/browser/login_database.cc b/components/password_manager/core/browser/login_database.cc
index 1401a59..dbb1249 100644
--- a/components/password_manager/core/browser/login_database.cc
+++ b/components/password_manager/core/browser/login_database.cc
@@ -1041,11 +1041,19 @@
   return list;
 }
 
-PasswordStoreChangeList LoginDatabase::UpdateLogin(const PasswordForm& form) {
+PasswordStoreChangeList LoginDatabase::UpdateLogin(const PasswordForm& form,
+                                                   UpdateLoginError* error) {
+  if (error) {
+    *error = UpdateLoginError::kNone;
+  }
   std::string encrypted_password;
   if (EncryptedString(form.password_value, &encrypted_password) !=
-      ENCRYPTION_RESULT_SUCCESS)
+      ENCRYPTION_RESULT_SUCCESS) {
+    if (error) {
+      *error = UpdateLoginError::kEncrytionServiceFailure;
+    }
     return PasswordStoreChangeList();
+  }
 
 #if defined(OS_IOS)
   DeleteEncryptedPassword(form);
@@ -1093,12 +1101,19 @@
   // NOTE: Add new fields here only if the field is a part of the unique key.
   // Otherwise, add the field above "WHERE starts here" comment.
 
-  if (!s.Run())
+  if (!s.Run()) {
+    if (error) {
+      *error = UpdateLoginError::kDbError;
+    }
     return PasswordStoreChangeList();
+  }
 
   PasswordStoreChangeList list;
-  if (db_.GetLastChangeCount())
+  if (db_.GetLastChangeCount()) {
     list.emplace_back(PasswordStoreChange::UPDATE, form, GetPrimaryKey(form));
+  } else if (error) {
+    *error = UpdateLoginError::kNoUpdatedRecords;
+  }
 
   return list;
 }
diff --git a/components/password_manager/core/browser/login_database.h b/components/password_manager/core/browser/login_database.h
index 616818ea..fe9a72f3 100644
--- a/components/password_manager/core/browser/login_database.h
+++ b/components/password_manager/core/browser/login_database.h
@@ -77,11 +77,13 @@
   PasswordStoreChangeList AddBlacklistedLoginForTesting(
       const autofill::PasswordForm& form) WARN_UNUSED_RESULT;
 
-  // Updates existing password form. Returns the list of applied changes
-  // ({}, {UPDATE}). The password is looked up by the tuple {origin,
-  // username_element, username_value, password_element, signon_realm}.
-  // These columns stay intact.
-  PasswordStoreChangeList UpdateLogin(const autofill::PasswordForm& form)
+  // Updates existing password form. Returns the list of applied changes ({},
+  // {UPDATE}). The password is looked up by the tuple {origin,
+  // username_element, username_value, password_element, signon_realm}. These
+  // columns stay intact. In case of error, it sets |error| if |error| isn't
+  // null.
+  PasswordStoreChangeList UpdateLogin(const autofill::PasswordForm& form,
+                                      UpdateLoginError* error = nullptr)
       WARN_UNUSED_RESULT;
 
   // Removes |form| from the list of remembered password forms. Returns true if
diff --git a/components/password_manager/core/browser/mock_password_store.h b/components/password_manager/core/browser/mock_password_store.h
index 9f36ae10..b7d28b2 100644
--- a/components/password_manager/core/browser/mock_password_store.h
+++ b/components/password_manager/core/browser/mock_password_store.h
@@ -32,8 +32,9 @@
   MOCK_METHOD2(AddLoginImpl,
                PasswordStoreChangeList(const autofill::PasswordForm&,
                                        AddLoginError* error));
-  MOCK_METHOD1(UpdateLoginImpl,
-               PasswordStoreChangeList(const autofill::PasswordForm&));
+  MOCK_METHOD2(UpdateLoginImpl,
+               PasswordStoreChangeList(const autofill::PasswordForm&,
+                                       UpdateLoginError* error));
   MOCK_METHOD1(RemoveLoginImpl,
                PasswordStoreChangeList(const autofill::PasswordForm&));
   MOCK_METHOD3(RemoveLoginsByURLAndTimeImpl,
diff --git a/components/password_manager/core/browser/password_store.cc b/components/password_manager/core/browser/password_store.cc
index 42fa7d6b..f38a26c 100644
--- a/components/password_manager/core/browser/password_store.cc
+++ b/components/password_manager/core/browser/password_store.cc
@@ -552,7 +552,8 @@
 }
 
 PasswordStoreChangeList PasswordStore::UpdateLoginSync(
-    const PasswordForm& form) {
+    const PasswordForm& form,
+    UpdateLoginError* error) {
   if (AffiliatedMatchHelper::IsValidAndroidCredential(
           PasswordStore::FormDigest(form))) {
     // Ideally, a |form| would not be updated in any way unless it was ensured
@@ -566,7 +567,7 @@
     if (old_form && form.password_value != old_form->password_value)
       ScheduleFindAndUpdateAffiliatedWebLogins(form);
   }
-  return UpdateLoginImpl(form);
+  return UpdateLoginImpl(form, error);
 }
 
 PasswordStoreChangeList PasswordStore::RemoveLoginSync(
diff --git a/components/password_manager/core/browser/password_store.h b/components/password_manager/core/browser/password_store.h
index 847e82d..e471931 100644
--- a/components/password_manager/core/browser/password_store.h
+++ b/components/password_manager/core/browser/password_store.h
@@ -394,7 +394,8 @@
   // Synchronous implementation provided by subclasses to update the given
   // login.
   virtual PasswordStoreChangeList UpdateLoginImpl(
-      const autofill::PasswordForm& form) = 0;
+      const autofill::PasswordForm& form,
+      UpdateLoginError* error = nullptr) = 0;
 
   // Synchronous implementation provided by subclasses to remove the given
   // login.
@@ -423,8 +424,8 @@
   // PasswordStoreSync:
   PasswordStoreChangeList AddLoginSync(const autofill::PasswordForm& form,
                                        AddLoginError* error) override;
-  PasswordStoreChangeList UpdateLoginSync(
-      const autofill::PasswordForm& form) override;
+  PasswordStoreChangeList UpdateLoginSync(const autofill::PasswordForm& form,
+                                          UpdateLoginError* error) override;
   PasswordStoreChangeList RemoveLoginSync(
       const autofill::PasswordForm& form) override;
 
diff --git a/components/password_manager/core/browser/password_store_default.cc b/components/password_manager/core/browser/password_store_default.cc
index 94318ba..f7ed5ff 100644
--- a/components/password_manager/core/browser/password_store_default.cc
+++ b/components/password_manager/core/browser/password_store_default.cc
@@ -68,11 +68,16 @@
 }
 
 PasswordStoreChangeList PasswordStoreDefault::UpdateLoginImpl(
-    const PasswordForm& form) {
+    const PasswordForm& form,
+    UpdateLoginError* error) {
   DCHECK(background_task_runner()->RunsTasksInCurrentSequence());
-  if (!login_db_)
+  if (!login_db_) {
+    if (error) {
+      *error = UpdateLoginError::kDbNotAvailable;
+    }
     return PasswordStoreChangeList();
-  return login_db_->UpdateLogin(form);
+  }
+  return login_db_->UpdateLogin(form, error);
 }
 
 PasswordStoreChangeList PasswordStoreDefault::RemoveLoginImpl(
diff --git a/components/password_manager/core/browser/password_store_default.h b/components/password_manager/core/browser/password_store_default.h
index e4fbb5d..20a5794 100644
--- a/components/password_manager/core/browser/password_store_default.h
+++ b/components/password_manager/core/browser/password_store_default.h
@@ -48,9 +48,9 @@
   void ReportMetricsImpl(const std::string& sync_username,
                          bool custom_passphrase_sync_enabled) override;
   PasswordStoreChangeList AddLoginImpl(const autofill::PasswordForm& form,
-                                       AddLoginError* error = nullptr) override;
-  PasswordStoreChangeList UpdateLoginImpl(
-      const autofill::PasswordForm& form) override;
+                                       AddLoginError* error) override;
+  PasswordStoreChangeList UpdateLoginImpl(const autofill::PasswordForm& form,
+                                          UpdateLoginError* error) override;
   PasswordStoreChangeList RemoveLoginImpl(
       const autofill::PasswordForm& form) override;
   PasswordStoreChangeList RemoveLoginsByURLAndTimeImpl(
diff --git a/components/password_manager/core/browser/password_store_sync.h b/components/password_manager/core/browser/password_store_sync.h
index e0455fc..0640c1c3 100644
--- a/components/password_manager/core/browser/password_store_sync.h
+++ b/components/password_manager/core/browser/password_store_sync.h
@@ -68,6 +68,27 @@
   kMaxValue = kDbError,
 };
 
+// Error values for updating a login in the store.
+// Used in metrics: "PasswordManager.MergeSyncData.UpdateLoginSyncError" and
+// "PasswordManager.ApplySyncChanges.UpdateLoginSyncError". These values are
+// persisted to logs. Entries should not be renumbered and numeric values should
+// never be reused.
+enum class UpdateLoginError {
+  // Success.
+  kNone = 0,
+  // Database not available.
+  kDbNotAvailable = 1,
+  // No records were updated.
+  kNoUpdatedRecords = 2,
+  // A service-level failure (e.g., on a platform using a keyring, the keyring
+  // is temporarily unavailable).
+  kEncrytionServiceFailure = 3,
+  // Database error.
+  kDbError = 4,
+
+  kMaxValue = kDbError,
+};
+
 // PasswordStore interface for PasswordSyncableService. It provides access to
 // synchronous methods of PasswordStore which shouldn't be accessible to other
 // classes. These methods are to be called on the PasswordStore background
@@ -114,7 +135,8 @@
 
   // Synchronous implementation to update the given login.
   virtual PasswordStoreChangeList UpdateLoginSync(
-      const autofill::PasswordForm& form) = 0;
+      const autofill::PasswordForm& form,
+      UpdateLoginError* error = nullptr) = 0;
 
   // Synchronous implementation to remove the given login.
   virtual PasswordStoreChangeList RemoveLoginSync(
diff --git a/components/password_manager/core/browser/password_store_unittest.cc b/components/password_manager/core/browser/password_store_unittest.cc
index 41cc43c..fd60f5c 100644
--- a/components/password_manager/core/browser/password_store_unittest.cc
+++ b/components/password_manager/core/browser/password_store_unittest.cc
@@ -705,9 +705,9 @@
           IgnoreResult(&PasswordStore::AddLoginSync), store,
           *expected_credentials_after_update[0], /*error=*/nullptr));
     } else {
-      store->ScheduleTask(
-          base::BindOnce(IgnoreResult(&PasswordStore::UpdateLoginSync), store,
-                         *expected_credentials_after_update[0]));
+      store->ScheduleTask(base::BindOnce(
+          IgnoreResult(&PasswordStore::UpdateLoginSync), store,
+          *expected_credentials_after_update[0], /*error=*/nullptr));
     }
     WaitForPasswordStore();
     store->RemoveObserver(&mock_observer);
diff --git a/components/password_manager/core/browser/sync/password_sync_bridge.cc b/components/password_manager/core/browser/sync/password_sync_bridge.cc
index c2b8029..76d55a7 100644
--- a/components/password_manager/core/browser/sync/password_sync_bridge.cc
+++ b/components/password_manager/core/browser/sync/password_sync_bridge.cc
@@ -392,10 +392,15 @@
             std::move(local_form_entity_data), metadata_change_list.get());
       } else {
         // The remote password is more recent, update the local model.
-        PasswordStoreChangeList changes =
-            password_store_sync_->UpdateLoginSync(PasswordFromEntityChange(
-                remote_entity_change, /*sync_time=*/time_now));
+        UpdateLoginError update_login_error;
+        PasswordStoreChangeList changes = password_store_sync_->UpdateLoginSync(
+            PasswordFromEntityChange(remote_entity_change,
+                                     /*sync_time=*/time_now),
+            &update_login_error);
         DCHECK_LE(changes.size(), 1U);
+        base::UmaHistogramEnumeration(
+            "PasswordManager.MergeSyncData.UpdateLoginSyncError",
+            update_login_error);
         if (changes.empty()) {
           metrics_util::LogPasswordSyncState(
               metrics_util::NOT_SYNCING_FAILED_UPDATE);
@@ -578,8 +583,13 @@
           if (entity_change->storage_key().empty()) {
             continue;
           }
+          UpdateLoginError update_login_error;
           changes = password_store_sync_->UpdateLoginSync(
-              PasswordFromEntityChange(*entity_change, /*sync_time=*/time_now));
+              PasswordFromEntityChange(*entity_change, /*sync_time=*/time_now),
+              &update_login_error);
+          base::UmaHistogramEnumeration(
+              "PasswordManager.ApplySyncChanges.UpdateLoginSyncError",
+              update_login_error);
           if (changes.empty()) {
             metrics_util::LogApplySyncChangesState(
                 metrics_util::ApplySyncChangesState::kApplyUpdateFailed);
diff --git a/components/password_manager/core/browser/sync/password_sync_bridge_unittest.cc b/components/password_manager/core/browser/sync/password_sync_bridge_unittest.cc
index 38a07ac0..726ddae 100644
--- a/components/password_manager/core/browser/sync/password_sync_bridge_unittest.cc
+++ b/components/password_manager/core/browser/sync/password_sync_bridge_unittest.cc
@@ -140,7 +140,11 @@
     return {PasswordStoreChange(PasswordStoreChange::ADD, form, primary_key)};
   }
 
-  PasswordStoreChangeList UpdateLogin(const autofill::PasswordForm& form) {
+  PasswordStoreChangeList UpdateLogin(const autofill::PasswordForm& form,
+                                      UpdateLoginError* error) {
+    if (error) {
+      *error = UpdateLoginError::kNone;
+    }
     int key = GetPrimaryKey(form);
     DCHECK_NE(-1, key);
     data_[key] = std::make_unique<autofill::PasswordForm>(form);
@@ -205,8 +209,9 @@
   MOCK_METHOD2(AddLoginSync,
                PasswordStoreChangeList(const autofill::PasswordForm&,
                                        AddLoginError*));
-  MOCK_METHOD1(UpdateLoginSync,
-               PasswordStoreChangeList(const autofill::PasswordForm&));
+  MOCK_METHOD2(UpdateLoginSync,
+               PasswordStoreChangeList(const autofill::PasswordForm&,
+                                       UpdateLoginError*));
   MOCK_METHOD1(RemoveLoginSync,
                PasswordStoreChangeList(const autofill::PasswordForm&));
   MOCK_METHOD1(NotifyLoginsChanged, void(const PasswordStoreChangeList&));
@@ -227,7 +232,7 @@
         .WillByDefault(Invoke(&fake_db_, &FakeDatabase::ReadAllLogins));
     ON_CALL(mock_password_store_sync_, AddLoginSync(_, _))
         .WillByDefault(Invoke(&fake_db_, &FakeDatabase::AddLogin));
-    ON_CALL(mock_password_store_sync_, UpdateLoginSync(_))
+    ON_CALL(mock_password_store_sync_, UpdateLoginSync(_, _))
         .WillByDefault(Invoke(&fake_db_, &FakeDatabase::UpdateLogin));
     ON_CALL(mock_password_store_sync_, RemoveLoginByPrimaryKeySync(_))
         .WillByDefault(Invoke(&fake_db_, &FakeDatabase::RemoveLogin));
@@ -443,7 +448,7 @@
   testing::InSequence in_sequence;
   EXPECT_CALL(*mock_password_store_sync(), BeginTransaction());
   EXPECT_CALL(*mock_password_store_sync(),
-              UpdateLoginSync(FormHasSignonRealm(kSignonRealm1)));
+              UpdateLoginSync(FormHasSignonRealm(kSignonRealm1), _));
   EXPECT_CALL(*mock_password_store_sync(), CommitTransaction());
   EXPECT_CALL(*mock_password_store_sync(),
               NotifyLoginsChanged(
@@ -569,7 +574,7 @@
       .InSequence(s2);
 
   EXPECT_CALL(*mock_password_store_sync(),
-              UpdateLoginSync(FormHasSignonRealm(kSignonRealm2)))
+              UpdateLoginSync(FormHasSignonRealm(kSignonRealm2), _))
       .InSequence(s3);
 
   EXPECT_CALL(*mock_password_store_sync(),
@@ -645,7 +650,7 @@
   // Since the remote Form 2 is more recent, it will be updated in the password
   // store.
   EXPECT_CALL(*mock_password_store_sync(),
-              UpdateLoginSync(FormHasSignonRealm(kSignonRealm2)));
+              UpdateLoginSync(FormHasSignonRealm(kSignonRealm2), _));
   syncer::EntityChangeList entity_change_list;
   entity_change_list.push_back(syncer::EntityChange::CreateAdd(
       /*storage_key=*/"", SpecificsToEntity(specifics1)));
diff --git a/components/password_manager/core/browser/sync/password_syncable_service.cc b/components/password_manager/core/browser/sync/password_syncable_service.cc
index f55a7a3..e60f079 100644
--- a/components/password_manager/core/browser/sync/password_syncable_service.cc
+++ b/components/password_manager/core/browser/sync/password_syncable_service.cc
@@ -385,8 +385,18 @@
 
   for (const std::unique_ptr<autofill::PasswordForm>& form :
        entries.updated_entries) {
+    UpdateLoginError update_login_error;
     PasswordStoreChangeList new_changes =
-        password_store_->UpdateLoginSync(*form);
+        password_store_->UpdateLoginSync(*form, &update_login_error);
+    if (is_merge) {
+      base::UmaHistogramEnumeration(
+          "PasswordManager.MergeSyncData.UpdateLoginSyncError",
+          update_login_error);
+    } else {
+      base::UmaHistogramEnumeration(
+          "PasswordManager.ApplySyncChanges.UpdateLoginSyncError",
+          update_login_error);
+    }
     changes.insert(changes.end(), new_changes.begin(), new_changes.end());
   }
 
diff --git a/components/password_manager/core/browser/sync/password_syncable_service_unittest.cc b/components/password_manager/core/browser/sync/password_syncable_service_unittest.cc
index 0f0db98..0b0abb5a6 100644
--- a/components/password_manager/core/browser/sync/password_syncable_service_unittest.cc
+++ b/components/password_manager/core/browser/sync/password_syncable_service_unittest.cc
@@ -180,8 +180,8 @@
         new PasswordSyncableService(password_store_->GetSyncInterface()));
 
     ON_CALL(*password_store_, AddLoginImpl(HasDateSynced(), _))
-        .WillByDefault([&](const autofill::PasswordForm& form,
-                           password_manager::AddLoginError* error) {
+        .WillByDefault([](const autofill::PasswordForm& form,
+                          password_manager::AddLoginError* error) {
           if (error) {
             *error = AddLoginError::kNone;
           }
@@ -189,8 +189,14 @@
         });
     ON_CALL(*password_store_, RemoveLoginImpl(_))
         .WillByDefault(Return(PasswordStoreChangeList()));
-    ON_CALL(*password_store_, UpdateLoginImpl(HasDateSynced()))
-        .WillByDefault(Return(PasswordStoreChangeList()));
+    ON_CALL(*password_store_, UpdateLoginImpl(HasDateSynced(), _))
+        .WillByDefault([](const autofill::PasswordForm& form,
+                          password_manager::UpdateLoginError* error) {
+          if (error) {
+            *error = UpdateLoginError::kNone;
+          }
+          return PasswordStoreChangeList();
+        });
     EXPECT_CALL(*password_store(), NotifyLoginsChanged(_)).Times(AnyNumber());
   }
 
@@ -330,7 +336,7 @@
   EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
       .WillOnce(AppendForm(form1));
   EXPECT_CALL(*password_store(), FillBlacklistLogins(_)).WillOnce(Return(true));
-  EXPECT_CALL(*password_store(), UpdateLoginImpl(PasswordIs(form2)));
+  EXPECT_CALL(*password_store(), UpdateLoginImpl(PasswordIs(form2), _));
   EXPECT_CALL(*processor_, ProcessSyncChanges(_, IsEmpty()));
 
   service()->MergeDataAndStartSyncing(
@@ -401,7 +407,7 @@
   list.push_back(
       CreateSyncChange(deleted_form, syncer::SyncChange::ACTION_DELETE));
   EXPECT_CALL(*password_store(), AddLoginImpl(PasswordIs(new_from_sync), _));
-  EXPECT_CALL(*password_store(), UpdateLoginImpl(PasswordIs(updated_form)));
+  EXPECT_CALL(*password_store(), UpdateLoginImpl(PasswordIs(updated_form), _));
   EXPECT_CALL(*password_store(), RemoveLoginImpl(PasswordIs(deleted_form)));
   service()->ProcessSyncChanges(FROM_HERE, list);
 }
diff --git a/components/password_manager/core/browser/test_password_store.cc b/components/password_manager/core/browser/test_password_store.cc
index 74906fa..cdee8f1 100644
--- a/components/password_manager/core/browser/test_password_store.cc
+++ b/components/password_manager/core/browser/test_password_store.cc
@@ -71,7 +71,8 @@
 }
 
 PasswordStoreChangeList TestPasswordStore::UpdateLoginImpl(
-    const autofill::PasswordForm& form) {
+    const autofill::PasswordForm& form,
+    UpdateLoginError* error) {
   PasswordStoreChangeList changes;
   std::vector<autofill::PasswordForm>& forms =
       stored_passwords_[form.signon_realm];
diff --git a/components/password_manager/core/browser/test_password_store.h b/components/password_manager/core/browser/test_password_store.h
index ed33d3f..1bfe5e2 100644
--- a/components/password_manager/core/browser/test_password_store.h
+++ b/components/password_manager/core/browser/test_password_store.h
@@ -48,8 +48,8 @@
   // PasswordStore interface
   PasswordStoreChangeList AddLoginImpl(const autofill::PasswordForm& form,
                                        AddLoginError* error) override;
-  PasswordStoreChangeList UpdateLoginImpl(
-      const autofill::PasswordForm& form) override;
+  PasswordStoreChangeList UpdateLoginImpl(const autofill::PasswordForm& form,
+                                          UpdateLoginError* error) override;
   PasswordStoreChangeList RemoveLoginImpl(
       const autofill::PasswordForm& form) override;
   std::vector<std::unique_ptr<autofill::PasswordForm>> FillMatchingLogins(
diff --git a/components/policy/core/common/cloud/cloud_policy_constants.cc b/components/policy/core/common/cloud/cloud_policy_constants.cc
index ed899e4..b8c9377 100644
--- a/components/policy/core/common/cloud/cloud_policy_constants.cc
+++ b/components/policy/core/common/cloud/cloud_policy_constants.cc
@@ -122,4 +122,6 @@
                      sizeof(kPolicyVerificationKey));
 }
 
+const char kPolicyFCMInvalidationSenderID[] = "1013309121859";
+
 }  // namespace policy
diff --git a/components/policy/core/common/cloud/cloud_policy_constants.h b/components/policy/core/common/cloud/cloud_policy_constants.h
index 9c5f01d..3a30c49 100644
--- a/components/policy/core/common/cloud/cloud_policy_constants.h
+++ b/components/policy/core/common/cloud/cloud_policy_constants.h
@@ -173,6 +173,10 @@
   ENTERPRISE,
 };
 
+// Sender ID of FCM (Firebase Cloud Messaging)
+// Policy Invalidation sender coming from the Firebase console.
+extern const char kPolicyFCMInvalidationSenderID[];
+
 }  // namespace policy
 
 #endif  // COMPONENTS_POLICY_CORE_COMMON_CLOUD_CLOUD_POLICY_CONSTANTS_H_
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index 8297b12..7716495 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -617,7 +617,6 @@
         'HighContrastEnabled',
         'VirtualKeyboardEnabled',
         'StickyKeysEnabled',
-        'DockedMagnifierEnabled',
         'KeyboardDefaultToFunctionKeys',
         'ScreenMagnifierType',
         'DeviceLoginScreenDefaultLargeCursorEnabled',
@@ -9359,30 +9358,6 @@
           If this policy is left unset, the sticky keys is disabled initially but can be enabled by the user anytime.'''
     },
     {
-      'name': 'DockedMagnifierEnabled',
-      'type': 'main',
-      'schema': { 'type': 'boolean' },
-      'supported_on': ['chrome_os:76-'],
-      'features': {
-        'can_be_recommended': True,
-        'dynamic_refresh': True,
-        'per_profile': True,
-      },
-      'example_value': True,
-      'id': 562,
-      'caption': '''Enable docked magnifier''',
-      'tags': [],
-      'desc': '''Enable the docked magnifier accessibility feature.
-
-          If this policy is set to true, the docked magnifier will always be enabled.
-
-          If this policy is set to false, the docked magnifier will always be disabled.
-
-          If you set this policy, users cannot change or override it.
-
-          If this policy is left unset, the docked magnifier is disabled initially but can be enabled by the user anytime.'''
-    },
-    {
       'name': 'KeyboardDefaultToFunctionKeys',
       'type': 'main',
       'schema': { 'type': 'boolean' },
@@ -9407,7 +9382,7 @@
       'type': 'int-enum',
       'schema': {
         'type': 'integer',
-        'enum': [ 0, 1 ],
+        'enum': [ 0, 1, 2 ],
       },
       'items': [
         {
@@ -9420,6 +9395,11 @@
           'value': 1,
           'caption': '''Full-screen magnifier enabled''',
         },
+        {
+          'name': 'Docked',
+          'value': 2,
+          'caption': '''Docked magnifier enabled''',
+        },
       ],
       'supported_on': ['chrome_os:29-'],
       'features': {
@@ -9534,7 +9514,7 @@
       'type': 'int-enum',
       'schema': {
         'type': 'integer',
-        'enum': [ 0, 1 ],
+        'enum': [ 0, 1, 2 ],
       },
       'items': [
         {
@@ -9547,6 +9527,11 @@
           'value': 1,
           'caption': '''Full-screen magnifier enabled''',
         },
+        {
+          'name': 'Docked',
+          'value': 2,
+          'caption': '''Docked magnifier enabled''',
+        },
       ],
       'supported_on': ['chrome_os:29-'],
       'device_only': True,
@@ -16851,7 +16836,7 @@
     },
   ],
   'placeholders': [],
-  'deleted_policy_ids': [412, 546],
+  'deleted_policy_ids': [412, 546, 562],
   'highest_id_currently_used': 569,
   'highest_atomic_group_id_currently_used': 37
 }
diff --git a/components/sync/BUILD.gn b/components/sync/BUILD.gn
index bb7a171..306c6a7b 100644
--- a/components/sync/BUILD.gn
+++ b/components/sync/BUILD.gn
@@ -312,6 +312,8 @@
     "model_impl/syncable_service_based_bridge.h",
     "nigori/cryptographer.cc",
     "nigori/cryptographer.h",
+    "nigori/forwarding_model_type_processor.cc",
+    "nigori/forwarding_model_type_processor.h",
     "nigori/keystore_keys_handler.h",
     "nigori/nigori.cc",
     "nigori/nigori.h",
diff --git a/components/sync/driver/DEPS b/components/sync/driver/DEPS
index b77b306..6e0f7ed 100644
--- a/components/sync/driver/DEPS
+++ b/components/sync/driver/DEPS
@@ -14,6 +14,7 @@
   "-components/signin/core/browser/signin_manager_base.h",
   "+components/sync/base",
   "+components/sync/engine",
+  "+components/sync/engine_impl",
   "+components/sync/js",
   "+components/sync/model",
   "+components/sync/model_impl",
diff --git a/components/sync/driver/glue/sync_engine_backend.cc b/components/sync/driver/glue/sync_engine_backend.cc
index f6cdade..e1c4640 100644
--- a/components/sync/driver/glue/sync_engine_backend.cc
+++ b/components/sync/driver/glue/sync_engine_backend.cc
@@ -17,6 +17,9 @@
 #include "components/invalidation/public/object_id_invalidation_map.h"
 #include "components/sync/base/invalidation_adapter.h"
 #include "components/sync/base/sync_base_switches.h"
+#include "components/sync/driver/configure_context.h"
+#include "components/sync/driver/model_type_controller.h"
+#include "components/sync/driver/sync_driver_switches.h"
 #include "components/sync/engine/cycle/commit_counters.h"
 #include "components/sync/engine/cycle/status_counters.h"
 #include "components/sync/engine/cycle/sync_cycle_snapshot.h"
@@ -27,6 +30,10 @@
 #include "components/sync/engine/sync_backend_registrar.h"
 #include "components/sync/engine/sync_manager.h"
 #include "components/sync/engine/sync_manager_factory.h"
+#include "components/sync/engine_impl/sync_encryption_handler_impl.h"
+#include "components/sync/model_impl/forwarding_model_type_controller_delegate.h"
+#include "components/sync/nigori/nigori_model_type_processor.h"
+#include "components/sync/nigori/nigori_sync_bridge_impl.h"
 #include "components/sync/syncable/directory.h"
 #include "components/sync/syncable/user_share.h"
 
@@ -141,6 +148,34 @@
   ModelTypeSet new_control_types =
       registrar_->ConfigureDataTypes(ControlTypes(), ModelTypeSet());
 
+  ModelTypeConnector* model_type_connector =
+      sync_manager_->GetModelTypeConnector();
+  if (nigori_controller_) {
+    // Having non-null |nigori_controller_| means that USS implementation of
+    // Nigori is enabled.
+    // The controller for Nigori is not exposed to the UI thread or the
+    // DataTypeManager, so we need to start it here manually.
+    ConfigureContext configure_context;
+    configure_context.authenticated_account_id = authenticated_account_id_;
+    configure_context.cache_guid = sync_manager_->cache_guid();
+    // TODO(crbug.com/922900): investigate whether we want to use
+    // STORAGE_IN_MEMORY in Butter mode.
+    configure_context.storage_option = STORAGE_ON_DISK;
+    configure_context.configuration_start_time = base::Time::Now();
+    nigori_controller_->LoadModels(configure_context, base::DoNothing());
+    DCHECK_EQ(nigori_controller_->state(), DataTypeController::MODEL_LOADED);
+    // TODO(crbug.com/922900): Do we need to call RegisterNonBlockingType() for
+    // Nigori?
+    model_type_connector->ConnectNonBlockingType(
+        NIGORI, nigori_controller_->ActivateManuallyForNigori());
+  } else {
+    // Control types don't have DataTypeControllers, but they need to have
+    // update handlers registered in ModelTypeRegistry.
+    for (ModelType control_type : ControlTypes()) {
+      model_type_connector->RegisterDirectoryType(control_type, GROUP_PASSIVE);
+    }
+  }
+
   ModelSafeRoutingInfo routing_info;
   registrar_->GetModelSafeRoutingInfo(&routing_info);
   SDVLOG(1) << "Control Types " << ModelTypeSetToString(new_control_types)
@@ -305,10 +340,26 @@
   // Load the previously persisted set of invalidation versions into memory.
   last_invalidation_versions_ = params.invalidation_versions;
 
+  authenticated_account_id_ = params.authenticated_account_id;
+
   DCHECK(!registrar_);
   DCHECK(params.registrar);
   registrar_ = std::move(params.registrar);
 
+  if (base::FeatureList::IsEnabled(switches::kSyncUSSNigori)) {
+    auto nigori_processor = std::make_unique<NigoriModelTypeProcessor>();
+    nigori_controller_ = std::make_unique<ModelTypeController>(
+        NIGORI, std::make_unique<ForwardingModelTypeControllerDelegate>(
+                    nigori_processor->GetControllerDelegate().get()));
+    sync_encryption_handler_ = std::make_unique<NigoriSyncBridgeImpl>(
+        std::move(nigori_processor), &encryptor_);
+  } else {
+    sync_encryption_handler_ = std::make_unique<SyncEncryptionHandlerImpl>(
+        &user_share_, &encryptor_, params.restored_key_for_bootstrapping,
+        params.restored_keystore_key_for_bootstrapping,
+        base::BindRepeating(&Nigori::GenerateScryptSalt));
+  }
+
   sync_manager_ = params.sync_manager_factory->CreateSyncManager(name_);
   sync_manager_->AddObserver(this);
 
@@ -329,11 +380,9 @@
   args.change_delegate = registrar_.get();  // as SyncManager::ChangeDelegate
   args.authenticated_account_id = params.authenticated_account_id;
   args.invalidator_client_id = params.invalidator_client_id;
-  args.restored_key_for_bootstrapping = params.restored_key_for_bootstrapping;
-  args.restored_keystore_key_for_bootstrapping =
-      params.restored_keystore_key_for_bootstrapping;
   args.engine_components_factory = std::move(params.engine_components_factory);
-  args.encryptor = &encryptor_;
+  args.user_share = &user_share_;
+  args.encryption_handler = sync_encryption_handler_.get();
   args.unrecoverable_error_handler = params.unrecoverable_error_handler;
   args.report_unrecoverable_error_function =
       params.report_unrecoverable_error_function;
@@ -459,8 +508,11 @@
 
   registrar_ = nullptr;
 
-  if (reason == DISABLE_SYNC)
+  if (reason == DISABLE_SYNC) {
+    // TODO(crbug.com/922900): We may want to remove Nigori data from the
+    // storage if USS Nigori implementation is enabled.
     syncable::Directory::DeleteDirectoryFiles(sync_data_folder_);
+  }
 
   host_.Reset();
   weak_ptr_factory_.InvalidateWeakPtrs();
diff --git a/components/sync/driver/glue/sync_engine_backend.h b/components/sync/driver/glue/sync_engine_backend.h
index e4ff0763b..e8183596 100644
--- a/components/sync/driver/glue/sync_engine_backend.h
+++ b/components/sync/driver/glue/sync_engine_backend.h
@@ -27,10 +27,12 @@
 #include "components/sync/engine/model_type_configurer.h"
 #include "components/sync/engine/shutdown_reason.h"
 #include "components/sync/engine/sync_encryption_handler.h"
+#include "components/sync/syncable/user_share.h"
 #include "url/gurl.h"
 
 namespace syncer {
 
+class ModelTypeController;
 class SyncEngineImpl;
 
 class SyncEngineBackend : public base::RefCountedThreadSafe<SyncEngineBackend>,
@@ -208,9 +210,25 @@
   // Our encryptor, which uses Chrome's encryption functions.
   SystemEncryptor encryptor_;
 
+  // We hold |user_share_| here as a dependency for |sync_encryption_handler_|.
+  // Should outlive |sync_encryption_handler_| and |sync_manager_|.
+  UserShare user_share_;
+
+  // Points to either SyncEncryptionHandlerImpl or NigoriSyncBridgeImpl
+  // depending on whether USS implementation of Nigori is enabled or not.
+  // Should outlive |sync_manager_|.
+  std::unique_ptr<SyncEncryptionHandler> sync_encryption_handler_;
+
   // The top-level syncapi entry point.  Lives on the sync thread.
   std::unique_ptr<SyncManager> sync_manager_;
 
+  // Required for |nigori_controller_| LoadModels().
+  std::string authenticated_account_id_;
+
+  // Initialized in OnInitializationComplete() iff USS implementation of Nigori
+  // is enabled.
+  std::unique_ptr<ModelTypeController> nigori_controller_;
+
   // Temporary holder of sync manager's initialization results. Set by
   // OnInitializeComplete, and consumed when we pass it via OnEngineInitialized
   // in the final state of HandleInitializationSuccessOnFrontendLoop.
diff --git a/components/sync/driver/model_type_controller.cc b/components/sync/driver/model_type_controller.cc
index 4dab5b0..456bef1 100644
--- a/components/sync/driver/model_type_controller.cc
+++ b/components/sync/driver/model_type_controller.cc
@@ -60,6 +60,17 @@
 
 ModelTypeController::~ModelTypeController() {}
 
+std::unique_ptr<DataTypeActivationResponse>
+ModelTypeController::ActivateManuallyForNigori() {
+  // To avoid abuse of this temporary API, we restrict it to NIGORI.
+  DCHECK_EQ(NIGORI, type());
+  DCHECK_EQ(MODEL_LOADED, state_);
+  DCHECK(activation_response_);
+  state_ = RUNNING;
+  activated_ = true;  // Not relevant, but for consistency.
+  return std::move(activation_response_);
+}
+
 bool ModelTypeController::ShouldLoadModelBeforeConfigure() const {
   // USS datatypes require loading models because model controls storage where
   // data type context and progress marker are persisted.
diff --git a/components/sync/driver/model_type_controller.h b/components/sync/driver/model_type_controller.h
index 9ba6e68..6dd17a1a 100644
--- a/components/sync/driver/model_type_controller.h
+++ b/components/sync/driver/model_type_controller.h
@@ -38,6 +38,12 @@
       std::unique_ptr<ModelTypeControllerDelegate> delegate_in_memory);
   ~ModelTypeController() override;
 
+  // Steals the activation response, only used for Nigori.
+  // TODO(crbug.com/967677): Once all datatypes are in USS, we should redesign
+  // or remove RegisterWithBackend, and expose the activation response via
+  // LoadModels(), which is more natural in USS.
+  std::unique_ptr<DataTypeActivationResponse> ActivateManuallyForNigori();
+
   // DataTypeController implementation.
   bool ShouldLoadModelBeforeConfigure() const override;
   void BeforeLoadModels(ModelTypeConfigurer* configurer) override;
diff --git a/components/sync/driver/non_ui_syncable_service_based_model_type_controller.cc b/components/sync/driver/non_ui_syncable_service_based_model_type_controller.cc
index c480da6..b4f487a14 100644
--- a/components/sync/driver/non_ui_syncable_service_based_model_type_controller.cc
+++ b/components/sync/driver/non_ui_syncable_service_based_model_type_controller.cc
@@ -17,7 +17,9 @@
 namespace {
 
 // Helper object that allows constructing and destructing the
-// SyncableServiceBasedBridge on the model thread.
+// SyncableServiceBasedBridge on the model thread. Gets constructed on the UI
+// thread, but all other operations including destruction happen on the model
+// thread.
 class BridgeBuilder {
  public:
   BridgeBuilder(
@@ -27,20 +29,21 @@
           syncable_service_provider,
       const base::RepeatingClosure& dump_stack,
       scoped_refptr<base::SequencedTaskRunner> task_runner)
-      : task_runner_(task_runner),
-        bridge_(nullptr, base::OnTaskRunnerDeleter(task_runner)),
-        weak_ptr_factory_(this) {
+      : task_runner_(task_runner) {
     DCHECK(store_factory);
     DCHECK(syncable_service_provider);
 
+    // Unretained is safe because destruction also happens on |task_runner_| and
+    // can't overtake this task.
     task_runner_->PostTask(
         FROM_HERE,
-        base::BindOnce(&BridgeBuilder::BuildBridgeDelegate,
-                       weak_ptr_factory_.GetWeakPtr(), type,
-                       std::move(store_factory),
+        base::BindOnce(&BridgeBuilder::InitOnModelThread,
+                       base::Unretained(this), type, std::move(store_factory),
                        std::move(syncable_service_provider), dump_stack));
   }
 
+  ~BridgeBuilder() { DCHECK(task_runner_->RunsTasksInCurrentSequence()); }
+
   base::WeakPtr<ModelTypeControllerDelegate> GetBridgeDelegate() {
     DCHECK(task_runner_->RunsTasksInCurrentSequence());
     DCHECK(bridge_);
@@ -48,7 +51,7 @@
   }
 
  private:
-  void BuildBridgeDelegate(
+  void InitOnModelThread(
       ModelType type,
       OnceModelTypeStoreFactory store_factory,
       NonUiSyncableServiceBasedModelTypeController::SyncableServiceProvider
@@ -61,22 +64,37 @@
         std::move(syncable_service_provider).Run();
     // |syncable_service| can be null in tests.
     if (syncable_service) {
-      // std::make_unique() avoided here due to custom deleter.
-      bridge_.reset(new SyncableServiceBasedBridge(
+      bridge_ = std::make_unique<SyncableServiceBasedBridge>(
           type, std::move(store_factory),
           std::make_unique<ClientTagBasedModelTypeProcessor>(type, dump_stack),
-          syncable_service.get()));
+          syncable_service.get());
     }
   }
 
   scoped_refptr<base::SequencedTaskRunner> task_runner_;
-  std::unique_ptr<ModelTypeSyncBridge, base::OnTaskRunnerDeleter> bridge_;
-
-  base::WeakPtrFactory<BridgeBuilder> weak_ptr_factory_;
+  std::unique_ptr<ModelTypeSyncBridge> bridge_;
 
   DISALLOW_COPY_AND_ASSIGN(BridgeBuilder);
 };
 
+ProxyModelTypeControllerDelegate::DelegateProvider BuildDelegateProvider(
+    ModelType type,
+    OnceModelTypeStoreFactory store_factory,
+    NonUiSyncableServiceBasedModelTypeController::SyncableServiceProvider
+        syncable_service_provider,
+    const base::RepeatingClosure& dump_stack,
+    scoped_refptr<base::SequencedTaskRunner> task_runner) {
+  // Can't use std::make_unique or base::WrapUnique because of custom deleter.
+  auto bridge_builder =
+      std::unique_ptr<BridgeBuilder, base::OnTaskRunnerDeleter>(
+          new BridgeBuilder(type, std::move(store_factory),
+                            std::move(syncable_service_provider), dump_stack,
+                            task_runner),
+          base::OnTaskRunnerDeleter(task_runner));
+  return base::BindRepeating(&BridgeBuilder::GetBridgeDelegate,
+                             std::move(bridge_builder));
+}
+
 }  // namespace
 
 NonUiSyncableServiceBasedModelTypeController::
@@ -90,13 +108,11 @@
           type,
           std::make_unique<ProxyModelTypeControllerDelegate>(
               task_runner,
-              base::BindRepeating(&BridgeBuilder::GetBridgeDelegate,
-                                  std::make_unique<BridgeBuilder>(
-                                      type,
-                                      std::move(store_factory),
-                                      std::move(syncable_service_provider),
-                                      dump_stack,
-                                      task_runner)))) {}
+              BuildDelegateProvider(type,
+                                    std::move(store_factory),
+                                    std::move(syncable_service_provider),
+                                    dump_stack,
+                                    task_runner))) {}
 
 NonUiSyncableServiceBasedModelTypeController::
     ~NonUiSyncableServiceBasedModelTypeController() {}
diff --git a/components/sync/driver/sync_driver_switches.cc b/components/sync/driver/sync_driver_switches.cc
index 6676277c..0ef33501 100644
--- a/components/sync/driver/sync_driver_switches.cc
+++ b/components/sync/driver/sync_driver_switches.cc
@@ -75,6 +75,10 @@
 const base::Feature kSyncUSSAutofillWalletMetadata{
     "SyncUSSAutofillWalletMetadata", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enable USS implementation of Nigori datatype.
+const base::Feature kSyncUSSNigori{"SyncUSSNigori",
+                                   base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Controls whether to enable syncing of Wi-Fi configurations.
 const base::Feature kSyncWifiConfigurations{"SyncWifiConfigurations",
                                             base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/components/sync/driver/sync_driver_switches.h b/components/sync/driver/sync_driver_switches.h
index 4ad8437..eff9abd9 100644
--- a/components/sync/driver/sync_driver_switches.h
+++ b/components/sync/driver/sync_driver_switches.h
@@ -34,6 +34,7 @@
 extern const base::Feature kSyncUSSBookmarks;
 extern const base::Feature kSyncUSSPasswords;
 extern const base::Feature kSyncUSSAutofillWalletMetadata;
+extern const base::Feature kSyncUSSNigori;
 extern const base::Feature kSyncWifiConfigurations;
 
 }  // namespace switches
diff --git a/components/sync/engine/fake_sync_manager.cc b/components/sync/engine/fake_sync_manager.cc
index 0577ca8..522e0d1 100644
--- a/components/sync/engine/fake_sync_manager.cc
+++ b/components/sync/engine/fake_sync_manager.cc
@@ -198,6 +198,10 @@
   return test_user_share_.user_share();
 }
 
+ModelTypeConnector* FakeSyncManager::GetModelTypeConnector() {
+  return &fake_model_type_connector_;
+}
+
 std::unique_ptr<ModelTypeConnector>
 FakeSyncManager::GetModelTypeConnectorProxy() {
   return std::make_unique<FakeModelTypeConnector>();
diff --git a/components/sync/engine/fake_sync_manager.h b/components/sync/engine/fake_sync_manager.h
index 82125b3..ac2d9fc 100644
--- a/components/sync/engine/fake_sync_manager.h
+++ b/components/sync/engine/fake_sync_manager.h
@@ -13,6 +13,7 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/observer_list.h"
+#include "components/sync/engine/fake_model_type_connector.h"
 #include "components/sync/engine/sync_manager.h"
 #include "components/sync/syncable/test_user_share.h"
 #include "components/sync/test/fake_sync_encryption_handler.h"
@@ -99,6 +100,7 @@
   void SaveChanges() override;
   void ShutdownOnSyncThread() override;
   UserShare* GetUserShare() override;
+  ModelTypeConnector* GetModelTypeConnector() override;
   std::unique_ptr<ModelTypeConnector> GetModelTypeConnectorProxy() override;
   std::string cache_guid() override;
   std::string birthday() override;
@@ -147,6 +149,8 @@
 
   FakeSyncEncryptionHandler fake_encryption_handler_;
 
+  FakeModelTypeConnector fake_model_type_connector_;
+
   TestUserShare test_user_share_;
 
   // Number of invalidations received since startup.
diff --git a/components/sync/engine/model_type_processor.h b/components/sync/engine/model_type_processor.h
index 671888ee..806543d 100644
--- a/components/sync/engine/model_type_processor.h
+++ b/components/sync/engine/model_type_processor.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "base/callback_forward.h"
 #include "components/sync/engine/non_blocking_sync_common.h"
 #include "components/sync/protocol/model_type_state.pb.h"
 
diff --git a/components/sync/engine/sync_encryption_handler.h b/components/sync/engine/sync_encryption_handler.h
index 6681818..17635ad9 100644
--- a/components/sync/engine/sync_encryption_handler.h
+++ b/components/sync/engine/sync_encryption_handler.h
@@ -16,8 +16,15 @@
 namespace syncer {
 
 class Cryptographer;
+class KeystoreKeysHandler;
 enum class PassphraseType;
 
+namespace syncable {
+
+class NigoriHandler;
+
+}  // namespace syncable
+
 // Reasons due to which Cryptographer might require a passphrase.
 enum PassphraseRequiredReason {
   REASON_PASSPHRASE_NOT_REQUIRED = 0,  // Initial value.
@@ -173,6 +180,24 @@
   // base::Time() in case migration isn't completed.
   virtual base::Time GetKeystoreMigrationTime() const = 0;
 
+  // Unsafe getter. Use only if sync is not up and running and there is no risk
+  // of other threads calling this. Returns the original cryptographer. This
+  // Cryptographer will always reflect the actual state of
+  // SyncEncryptionHandler (no needs to call this method again in case
+  // cryptographer state was changed).
+  // TODO(crbug.com/970213): remove this method or replace with normal getter
+  // for const Cryptographer.
+  virtual Cryptographer* GetCryptographerUnsafe() = 0;
+
+  // Returns KeystoreKeysHandler, allowing to pass new keystore keys and to
+  // check whether keystore keys need to be requested from the server.
+  virtual KeystoreKeysHandler* GetKeystoreKeysHandler() = 0;
+
+  // Returns NigoriHandler, allowing directory-specific interaction with
+  // Nigori. Returns nullptr iff USS implementation of Nigori is enabled.
+  // TODO(crbug.com/970213): remove this method.
+  virtual syncable::NigoriHandler* GetNigoriHandler() = 0;
+
   // The set of types that are always encrypted.
   static ModelTypeSet SensitiveTypes();
 };
diff --git a/components/sync/engine/sync_engine_switches.cc b/components/sync/engine/sync_engine_switches.cc
index e05efd0..d5a3c05 100644
--- a/components/sync/engine/sync_engine_switches.cc
+++ b/components/sync/engine/sync_engine_switches.cc
@@ -25,8 +25,4 @@
 const base::Feature kSyncUseScryptForNewCustomPassphrases{
     "SyncUseScryptForNewCustomPassphrases", base::FEATURE_ENABLED_BY_DEFAULT};
 
-// Enable USS implementation of Nigori datatype.
-const base::Feature kSyncUSSNigori{"SyncUSSNigori",
-                                   base::FEATURE_DISABLED_BY_DEFAULT};
-
 }  // namespace switches
diff --git a/components/sync/engine/sync_engine_switches.h b/components/sync/engine/sync_engine_switches.h
index 5c5eedb..6861251 100644
--- a/components/sync/engine/sync_engine_switches.h
+++ b/components/sync/engine/sync_engine_switches.h
@@ -14,8 +14,6 @@
 extern const base::Feature kSyncResetPollIntervalOnStart;
 extern const base::Feature kSyncUseScryptForNewCustomPassphrases;
 
-extern const base::Feature kSyncUSSNigori;
-
 }  // namespace switches
 
 #endif  // COMPONENTS_SYNC_ENGINE_SYNC_ENGINE_SWITCHES_H_
diff --git a/components/sync/engine/sync_manager.cc b/components/sync/engine/sync_manager.cc
index 6737a4b0..12cc280 100644
--- a/components/sync/engine/sync_manager.cc
+++ b/components/sync/engine/sync_manager.cc
@@ -16,7 +16,8 @@
     : enable_local_sync_backend(false),
       extensions_activity(nullptr),
       change_delegate(nullptr),
-      encryptor(nullptr),
+      user_share(nullptr),
+      encryption_handler(nullptr),
       cancelation_signal(nullptr) {}
 
 SyncManager::InitArgs::~InitArgs() {}
diff --git a/components/sync/engine/sync_manager.h b/components/sync/engine/sync_manager.h
index 9dbc2298..d584a07b 100644
--- a/components/sync/engine/sync_manager.h
+++ b/components/sync/engine/sync_manager.h
@@ -45,7 +45,6 @@
 class BaseTransaction;
 class CancelationSignal;
 class DataTypeDebugInfoListener;
-class Encryptor;
 class EngineComponentsFactory;
 class ExtensionsActivity;
 class JsBackend;
@@ -226,14 +225,13 @@
     // Unqiuely identifies this client to the invalidation notification server.
     std::string invalidator_client_id;
 
-    // Used to boostrap the cryptographer.
-    std::string restored_key_for_bootstrapping;
-    std::string restored_keystore_key_for_bootstrapping;
-
     std::unique_ptr<EngineComponentsFactory> engine_components_factory;
 
     // Must outlive SyncManager.
-    Encryptor* encryptor;
+    UserShare* user_share;
+
+    // Must outlive SyncManager.
+    SyncEncryptionHandler* encryption_handler;
 
     WeakHandle<UnrecoverableErrorHandler> unrecoverable_error_handler;
     base::Closure report_unrecoverable_error_function;
@@ -341,6 +339,11 @@
   // May be called from any thread.
   virtual UserShare* GetUserShare() = 0;
 
+  // Returns non-owning pointer to ModelTypeConnector. In contrast with
+  // ModelTypeConnectorProxy all calls are executed synchronously, thus the
+  // pointer should be used on sync thread.
+  virtual ModelTypeConnector* GetModelTypeConnector() = 0;
+
   // Returns an instance of the main interface for registering sync types with
   // sync engine.
   virtual std::unique_ptr<ModelTypeConnector> GetModelTypeConnectorProxy() = 0;
diff --git a/components/sync/engine_impl/sync_encryption_handler_impl.cc b/components/sync/engine_impl/sync_encryption_handler_impl.cc
index 048506b6..e3dd31d 100644
--- a/components/sync/engine_impl/sync_encryption_handler_impl.cc
+++ b/components/sync/engine_impl/sync_encryption_handler_impl.cc
@@ -743,6 +743,21 @@
   return keystore_migration_time_;
 }
 
+Cryptographer* SyncEncryptionHandlerImpl::GetCryptographerUnsafe() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return &vault_unsafe_.cryptographer;
+}
+
+KeystoreKeysHandler* SyncEncryptionHandlerImpl::GetKeystoreKeysHandler() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return this;
+}
+
+syncable::NigoriHandler* SyncEncryptionHandlerImpl::GetNigoriHandler() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return this;
+}
+
 // Note: this is called from within a syncable transaction, so we need to post
 // tasks if we want to do any work that creates a new sync_api transaction.
 void SyncEncryptionHandlerImpl::ApplyNigoriUpdate(
@@ -867,11 +882,6 @@
   return UnlockVault(trans).passphrase_type;
 }
 
-Cryptographer* SyncEncryptionHandlerImpl::GetCryptographerUnsafe() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  return &vault_unsafe_.cryptographer;
-}
-
 ModelTypeSet SyncEncryptionHandlerImpl::GetEncryptedTypesUnsafe() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return vault_unsafe_.encrypted_types;
diff --git a/components/sync/engine_impl/sync_encryption_handler_impl.h b/components/sync/engine_impl/sync_encryption_handler_impl.h
index 35e6a31..fdd4e83 100644
--- a/components/sync/engine_impl/sync_encryption_handler_impl.h
+++ b/components/sync/engine_impl/sync_encryption_handler_impl.h
@@ -69,6 +69,9 @@
   void EnableEncryptEverything() override;
   bool IsEncryptEverythingEnabled() const override;
   base::Time GetKeystoreMigrationTime() const override;
+  Cryptographer* GetCryptographerUnsafe() override;
+  KeystoreKeysHandler* GetKeystoreKeysHandler() override;
+  syncable::NigoriHandler* GetNigoriHandler() override;
 
   // NigoriHandler implementation.
   // Note: all methods are invoked while the caller holds a transaction.
@@ -89,7 +92,7 @@
 
   // Unsafe getters. Use only if sync is not up and running and there is no risk
   // of other threads calling this.
-  Cryptographer* GetCryptographerUnsafe();
+
   ModelTypeSet GetEncryptedTypesUnsafe();
 
   bool MigratedToKeystore();
diff --git a/components/sync/engine_impl/sync_manager_impl.cc b/components/sync/engine_impl/sync_manager_impl.cc
index 4d43b0d..5de0403 100644
--- a/components/sync/engine_impl/sync_manager_impl.cc
+++ b/components/sync/engine_impl/sync_manager_impl.cc
@@ -26,7 +26,6 @@
 #include "components/sync/engine/engine_util.h"
 #include "components/sync/engine/net/http_post_provider_factory.h"
 #include "components/sync/engine/polling_constants.h"
-#include "components/sync/engine/sync_engine_switches.h"
 #include "components/sync/engine_impl/cycle/directory_type_debug_info_emitter.h"
 #include "components/sync/engine_impl/loopback_server/loopback_connection_manager.h"
 #include "components/sync/engine_impl/model_type_connector_proxy.h"
@@ -35,9 +34,8 @@
 #include "components/sync/engine_impl/sync_scheduler.h"
 #include "components/sync/engine_impl/syncer_types.h"
 #include "components/sync/engine_impl/uss_migrator.h"
+#include "components/sync/nigori/cryptographer.h"
 #include "components/sync/nigori/nigori.h"
-#include "components/sync/nigori/nigori_model_type_processor.h"
-#include "components/sync/nigori/nigori_sync_bridge_impl.h"
 #include "components/sync/protocol/sync.pb.h"
 #include "components/sync/syncable/base_node.h"
 #include "components/sync/syncable/directory.h"
@@ -147,9 +145,11 @@
     network::NetworkConnectionTracker* network_connection_tracker)
     : name_(name),
       network_connection_tracker_(network_connection_tracker),
+      share_(nullptr),
       change_delegate_(nullptr),
       initialized_(false),
       observing_network_connectivity_changes_(false),
+      sync_encryption_handler_(nullptr),
       weak_ptr_factory_(this) {
   // Pre-fill |notification_info_map_|.
   for (int i = FIRST_REAL_MODEL_TYPE; i < ModelType::NUM_ENTRIES; ++i) {
@@ -296,29 +296,11 @@
   report_unrecoverable_error_function_ =
       args->report_unrecoverable_error_function;
 
-  allstatus_.SetHasKeystoreKey(
-      !args->restored_keystore_key_for_bootstrapping.empty());
+  DCHECK(args->user_share);
+  share_ = args->user_share;
 
-  Cryptographer* cryptographer_for_directory = nullptr;
-  syncable::NigoriHandler* nigori_handler = nullptr;
-  KeystoreKeysHandler* keystore_keys_handler = nullptr;
-  if (base::FeatureList::IsEnabled(switches::kSyncUSSNigori)) {
-    auto nigori_sync_bridge_impl = std::make_unique<NigoriSyncBridgeImpl>(
-        std::make_unique<NigoriModelTypeProcessor>(), args->encryptor);
-    keystore_keys_handler = nigori_sync_bridge_impl.get();
-    sync_encryption_handler_ = std::move(nigori_sync_bridge_impl);
-  } else {
-    auto sync_encryption_handler_impl =
-        std::make_unique<SyncEncryptionHandlerImpl>(
-            &share_, args->encryptor, args->restored_key_for_bootstrapping,
-            args->restored_keystore_key_for_bootstrapping,
-            base::BindRepeating(&Nigori::GenerateScryptSalt));
-    cryptographer_for_directory =
-        sync_encryption_handler_impl->GetCryptographerUnsafe();
-    nigori_handler = sync_encryption_handler_impl.get();
-    keystore_keys_handler = sync_encryption_handler_impl.get();
-    sync_encryption_handler_ = std::move(sync_encryption_handler_impl);
-  }
+  DCHECK(args->encryption_handler);
+  sync_encryption_handler_ = args->encryption_handler;
 
   // Register for encryption related changes now. We have to do this before
   // the initial download of control types or initializing the encryption
@@ -356,12 +338,13 @@
 
   DCHECK(backing_store);
 
-  // Note: |nigori_handler| and |cryptographer_for_directory| are nullptrs iff
-  // kSyncUSSNigori is enabled.
-  share_.directory = std::make_unique<syncable::Directory>(
+  // Note: NigoriHandler and Cryptographer passed to Directory are nullptrs iff
+  // USS implementation of Nigori is enabled.
+  share_->directory = std::make_unique<syncable::Directory>(
       std::move(backing_store), args->unrecoverable_error_handler,
-      report_unrecoverable_error_function_, nigori_handler,
-      cryptographer_for_directory);
+      report_unrecoverable_error_function_,
+      sync_encryption_handler_->GetNigoriHandler(),
+      sync_encryption_handler_->GetCryptographerUnsafe());
 
   DVLOG(1) << "AccountId: " << args->authenticated_account_id;
   if (!OpenDirectory(args)) {
@@ -370,6 +353,9 @@
     return;
   }
 
+  allstatus_.SetHasKeystoreKey(
+      !sync_encryption_handler_->GetKeystoreKeysHandler()->NeedKeystoreKey());
+
   if (args->enable_local_sync_backend) {
     VLOG(1) << "Running against local sync backend.";
     allstatus_.SetLocalBackendFolder(
@@ -394,8 +380,9 @@
   allstatus_.SetInvalidatorClientId(args->invalidator_client_id);
 
   model_type_registry_ = std::make_unique<ModelTypeRegistry>(
-      args->workers, &share_, this, base::Bind(&MigrateDirectoryData),
-      args->cancelation_signal, keystore_keys_handler);
+      args->workers, share_, this, base::Bind(&MigrateDirectoryData),
+      args->cancelation_signal,
+      sync_encryption_handler_->GetKeystoreKeysHandler());
   sync_encryption_handler_->AddObserver(model_type_registry_.get());
 
   // Build a SyncCycleContext and store the worker in it.
@@ -422,12 +409,6 @@
     scheduler_->OnCredentialsUpdated();
   }
 
-  // Control types don't have DataTypeControllers, but they need to have
-  // update handlers registered in ModelTypeRegistry.
-  for (ModelType control_type : ControlTypes()) {
-    model_type_registry_->RegisterDirectoryType(control_type, GROUP_PASSIVE);
-  }
-
   NotifyInitializationSuccess();
 }
 
@@ -505,7 +486,8 @@
 }
 
 syncable::Directory* SyncManagerImpl::directory() {
-  return share_.directory.get();
+  DCHECK(share_);
+  return share_->directory.get();
 }
 
 const SyncScheduler* SyncManagerImpl::scheduler() const {
@@ -671,7 +653,10 @@
     directory()->SaveChanges();
   }
 
-  share_.directory.reset();
+  // TODO(crbug.com/922900): can this be replaced with DCHECK(share_)?
+  if (share_) {
+    share_->directory.reset();
+  }
 
   change_delegate_ = nullptr;
 
@@ -1009,7 +994,13 @@
 
 UserShare* SyncManagerImpl::GetUserShare() {
   DCHECK(initialized_);
-  return &share_;
+  DCHECK(share_);
+  return share_;
+}
+
+ModelTypeConnector* SyncManagerImpl::GetModelTypeConnector() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return model_type_registry_.get();
 }
 
 std::unique_ptr<ModelTypeConnector>
@@ -1042,7 +1033,8 @@
 }
 
 SyncEncryptionHandler* SyncManagerImpl::GetEncryptionHandler() {
-  return sync_encryption_handler_.get();
+  DCHECK(sync_encryption_handler_);
+  return sync_encryption_handler_;
 }
 
 std::vector<std::unique_ptr<ProtocolEvent>>
diff --git a/components/sync/engine_impl/sync_manager_impl.h b/components/sync/engine_impl/sync_manager_impl.h
index e9b9785..a3c45a2b 100644
--- a/components/sync/engine_impl/sync_manager_impl.h
+++ b/components/sync/engine_impl/sync_manager_impl.h
@@ -28,14 +28,13 @@
 #include "components/sync/engine_impl/nudge_handler.h"
 #include "components/sync/engine_impl/sync_engine_event_listener.h"
 #include "components/sync/js/js_backend.h"
-#include "components/sync/nigori/cryptographer.h"
 #include "components/sync/syncable/change_reorder_buffer.h"
 #include "components/sync/syncable/directory_change_delegate.h"
-#include "components/sync/syncable/user_share.h"
 #include "services/network/public/cpp/network_connection_tracker.h"
 
 namespace syncer {
 
+class Cryptographer;
 class ModelTypeRegistry;
 class SyncCycleContext;
 class TypeDebugInfoObserver;
@@ -92,6 +91,7 @@
   void SaveChanges() override;
   void ShutdownOnSyncThread() override;
   UserShare* GetUserShare() override;
+  ModelTypeConnector* GetModelTypeConnector() override;
   std::unique_ptr<ModelTypeConnector> GetModelTypeConnectorProxy() override;
   std::string cache_guid() override;
   std::string birthday() override;
@@ -255,7 +255,7 @@
 
   // We give a handle to share_ to clients of the API for use when constructing
   // any transaction type.
-  UserShare share_;
+  UserShare* share_;
 
   // This can be called from any thread, but only between calls to
   // OpenDirectory() and ShutdownOnSyncThread().
@@ -315,9 +315,7 @@
 
   base::Closure report_unrecoverable_error_function_;
 
-  // Points to either SyncEncryptionHandlerImpl or NigoriSyncBridgeImpl
-  // depending on whether USS implementation of Nigori is enabled or not.
-  std::unique_ptr<SyncEncryptionHandler> sync_encryption_handler_;
+  SyncEncryptionHandler* sync_encryption_handler_;
 
   std::unique_ptr<SyncEncryptionHandler::Observer> encryption_observer_proxy_;
 
diff --git a/components/sync/engine_impl/sync_manager_impl_unittest.cc b/components/sync/engine_impl/sync_manager_impl_unittest.cc
index 6ee6925..2fda433 100644
--- a/components/sync/engine_impl/sync_manager_impl_unittest.cc
+++ b/components/sync/engine_impl/sync_manager_impl_unittest.cc
@@ -35,6 +35,7 @@
 #include "components/sync/engine/polling_constants.h"
 #include "components/sync/engine/test_engine_components_factory.h"
 #include "components/sync/engine_impl/cycle/sync_cycle.h"
+#include "components/sync/engine_impl/sync_encryption_handler_impl.h"
 #include "components/sync/engine_impl/sync_scheduler.h"
 #include "components/sync/engine_impl/test_entry_factory.h"
 #include "components/sync/js/js_event_handler.h"
@@ -952,6 +953,11 @@
         std::make_unique<StrictMock<SyncEncryptionHandlerObserverMock>>();
     encryption_observer_ = encryption_observer.get();
 
+    encryption_handler_ = std::make_unique<SyncEncryptionHandlerImpl>(
+        &user_share_, &encryptor_, /*restored_key_for_bootstrapping=*/"",
+        /*restored_keystore_key_for_bootstrapping=*/"",
+        base::BindRepeating(&Nigori::GenerateScryptSalt));
+
     SyncManager::InitArgs args;
     args.database_location = temp_dir_.GetPath();
     args.service_url = GURL("https://example.com/");
@@ -967,7 +973,8 @@
     args.enable_local_sync_backend = enable_local_sync_backend;
     args.local_sync_backend_folder = temp_dir_.GetPath();
     args.engine_components_factory.reset(GetFactory());
-    args.encryptor = &encryptor_;
+    args.user_share = &user_share_;
+    args.encryption_handler = encryption_handler_.get();
     args.unrecoverable_error_handler =
         MakeWeakHandle(mock_unrecoverable_error_handler_.GetWeakPtr());
     args.cancelation_signal = &cancelation_signal_;
@@ -1163,6 +1170,8 @@
 
  protected:
   FakeEncryptor encryptor_;
+  UserShare user_share_;
+  std::unique_ptr<SyncEncryptionHandler> encryption_handler_;
   SyncManagerImpl sync_manager_;
   CancelationSignal cancelation_signal_;
   WeakHandle<JsBackend> js_backend_;
diff --git a/components/sync/nigori/forwarding_model_type_processor.cc b/components/sync/nigori/forwarding_model_type_processor.cc
new file mode 100644
index 0000000..eca08cff
--- /dev/null
+++ b/components/sync/nigori/forwarding_model_type_processor.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 "components/sync/nigori/forwarding_model_type_processor.h"
+
+#include <utility>
+#include "base/callback.h"
+#include "components/sync/engine/commit_queue.h"
+
+namespace syncer {
+
+ForwardingModelTypeProcessor::ForwardingModelTypeProcessor(
+    ModelTypeProcessor* processor)
+    : processor_(processor) {
+  DCHECK(processor_);
+}
+
+ForwardingModelTypeProcessor::~ForwardingModelTypeProcessor() = default;
+
+void ForwardingModelTypeProcessor::ConnectSync(
+    std::unique_ptr<CommitQueue> worker) {
+  processor_->ConnectSync(std::move(worker));
+}
+
+void ForwardingModelTypeProcessor::DisconnectSync() {
+  processor_->DisconnectSync();
+}
+
+void ForwardingModelTypeProcessor::GetLocalChanges(
+    size_t max_entries,
+    GetLocalChangesCallback callback) {
+  processor_->GetLocalChanges(max_entries, std::move(callback));
+}
+
+void ForwardingModelTypeProcessor::OnCommitCompleted(
+    const sync_pb::ModelTypeState& type_state,
+    const CommitResponseDataList& response_list) {
+  processor_->OnCommitCompleted(type_state, response_list);
+}
+
+void ForwardingModelTypeProcessor::OnUpdateReceived(
+    const sync_pb::ModelTypeState& type_state,
+    UpdateResponseDataList updates) {
+  processor_->OnUpdateReceived(type_state, std::move(updates));
+}
+
+}  // namespace syncer
diff --git a/components/sync/nigori/forwarding_model_type_processor.h b/components/sync/nigori/forwarding_model_type_processor.h
new file mode 100644
index 0000000..98bc5493
--- /dev/null
+++ b/components/sync/nigori/forwarding_model_type_processor.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 COMPONENTS_SYNC_NIGORI_FORWARDING_MODEL_TYPE_PROCESSOR_H_
+#define COMPONENTS_SYNC_NIGORI_FORWARDING_MODEL_TYPE_PROCESSOR_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "components/sync/engine/model_type_processor.h"
+
+namespace syncer {
+
+// Trivial implementation of ModelTypeProcessor, that simply forwards
+// call to another processor. This is useful when an API requires transferring
+// ownership, but the calling site also wants to keep ownership of the actual
+// implementation, and can guarantee the lifetime constraints.
+class ForwardingModelTypeProcessor : public ModelTypeProcessor {
+ public:
+  // |processor| must not be null and must outlive this object.
+  explicit ForwardingModelTypeProcessor(ModelTypeProcessor* processor);
+  ~ForwardingModelTypeProcessor() override;
+
+  void ConnectSync(std::unique_ptr<CommitQueue> worker) override;
+  void DisconnectSync() override;
+  void GetLocalChanges(size_t max_entries,
+                       GetLocalChangesCallback callback) override;
+  void OnCommitCompleted(const sync_pb::ModelTypeState& type_state,
+                         const CommitResponseDataList& response_list) override;
+  void OnUpdateReceived(const sync_pb::ModelTypeState& type_state,
+                        UpdateResponseDataList updates) override;
+
+ private:
+  ModelTypeProcessor* const processor_;
+
+  DISALLOW_COPY_AND_ASSIGN(ForwardingModelTypeProcessor);
+};
+
+}  // namespace syncer
+
+#endif  // COMPONENTS_SYNC_NIGORI_FORWARDING_MODEL_TYPE_PROCESSOR_H_
diff --git a/components/sync/nigori/nigori_model_type_processor.cc b/components/sync/nigori/nigori_model_type_processor.cc
index 56bc044..604ede7 100644
--- a/components/sync/nigori/nigori_model_type_processor.cc
+++ b/components/sync/nigori/nigori_model_type_processor.cc
@@ -8,8 +8,8 @@
 #include "components/sync/base/data_type_histogram.h"
 #include "components/sync/base/time.h"
 #include "components/sync/engine/commit_queue.h"
-#include "components/sync/engine/model_type_processor_proxy.h"
 #include "components/sync/model_impl/processor_entity.h"
+#include "components/sync/nigori/forwarding_model_type_processor.h"
 #include "components/sync/nigori/nigori_sync_bridge.h"
 #include "components/sync/protocol/proto_memory_estimations.h"
 #include "components/sync/protocol/proto_value_conversions.h"
@@ -26,9 +26,7 @@
 }  // namespace
 
 NigoriModelTypeProcessor::NigoriModelTypeProcessor()
-    : bridge_(nullptr),
-      weak_ptr_factory_for_controller_(this),
-      weak_ptr_factory_for_worker_(this) {}
+    : bridge_(nullptr), weak_ptr_factory_for_controller_(this) {}
 
 NigoriModelTypeProcessor::~NigoriModelTypeProcessor() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -48,7 +46,6 @@
   DCHECK(IsConnected());
 
   DVLOG(1) << "Disconnecting sync for Encryption Keys";
-  weak_ptr_factory_for_worker_.InvalidateWeakPtrs();
   worker_.reset();
   if (entity_) {
     entity_->ClearTransientSyncState();
@@ -226,9 +223,6 @@
       break;
     }
   }
-
-  // Do not let any delayed callbacks to be called.
-  weak_ptr_factory_for_worker_.InvalidateWeakPtrs();
 }
 
 void NigoriModelTypeProcessor::GetAllNodesForDebugging(
@@ -421,9 +415,7 @@
   auto activation_response = std::make_unique<DataTypeActivationResponse>();
   activation_response->model_type_state = model_type_state_;
   activation_response->type_processor =
-      std::make_unique<ModelTypeProcessorProxy>(
-          weak_ptr_factory_for_worker_.GetWeakPtr(),
-          base::SequencedTaskRunnerHandle::Get());
+      std::make_unique<ForwardingModelTypeProcessor>(this);
   std::move(start_callback_).Run(std::move(activation_response));
 }
 
diff --git a/components/sync/nigori/nigori_model_type_processor.h b/components/sync/nigori/nigori_model_type_processor.h
index db9f593..48af934 100644
--- a/components/sync/nigori/nigori_model_type_processor.h
+++ b/components/sync/nigori/nigori_model_type_processor.h
@@ -104,9 +104,6 @@
   base::WeakPtrFactory<ModelTypeControllerDelegate>
       weak_ptr_factory_for_controller_;
 
-  // WeakPtrFactory for this processor which will be sent to sync thread.
-  base::WeakPtrFactory<NigoriModelTypeProcessor> weak_ptr_factory_for_worker_;
-
   DISALLOW_COPY_AND_ASSIGN(NigoriModelTypeProcessor);
 };
 
diff --git a/components/sync/nigori/nigori_model_type_processor_unittest.cc b/components/sync/nigori/nigori_model_type_processor_unittest.cc
index 38de778..abfdbe3 100644
--- a/components/sync/nigori/nigori_model_type_processor_unittest.cc
+++ b/components/sync/nigori/nigori_model_type_processor_unittest.cc
@@ -10,7 +10,6 @@
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/test/mock_callback.h"
-#include "base/test/scoped_task_environment.h"
 #include "components/sync/base/time.h"
 #include "components/sync/engine/commit_queue.h"
 #include "components/sync/nigori/nigori_sync_bridge.h"
@@ -146,10 +145,6 @@
   NigoriModelTypeProcessor* processor() { return &processor_; }
 
  private:
-  // This sets SequencedTaskRunnerHandle on the current thread, which the type
-  // processor will pick up as the sync task runner.
-  base::test::ScopedTaskEnvironment task_environment_;
-
   testing::NiceMock<MockNigoriSyncBridge> mock_nigori_sync_bridge_;
   std::unique_ptr<testing::NiceMock<MockCommitQueue>> mock_commit_queue_;
   MockCommitQueue* mock_commit_queue_ptr_;
@@ -407,9 +402,6 @@
   ASSERT_FALSE(processor()->IsConnectedForTest());
   captured_response->type_processor->ConnectSync(
       std::make_unique<testing::NiceMock<MockCommitQueue>>());
-  // RunUntilIdle() is needed because ModelTypeProcessorProxy() is used and it
-  // internally does posting of tasks.
-  base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(processor()->IsConnectedForTest());
 }
 
diff --git a/components/sync/nigori/nigori_sync_bridge_impl.cc b/components/sync/nigori/nigori_sync_bridge_impl.cc
index 400a1eb3..fe7f451 100644
--- a/components/sync/nigori/nigori_sync_bridge_impl.cc
+++ b/components/sync/nigori/nigori_sync_bridge_impl.cc
@@ -314,6 +314,25 @@
   return base::Time();
 }
 
+Cryptographer* NigoriSyncBridgeImpl::GetCryptographerUnsafe() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // This method exposes Cryptographer to Directory, and it's redundant in case
+  // USS implementation is enabled.
+  return nullptr;
+}
+
+KeystoreKeysHandler* NigoriSyncBridgeImpl::GetKeystoreKeysHandler() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return this;
+}
+
+syncable::NigoriHandler* NigoriSyncBridgeImpl::GetNigoriHandler() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // Note: GetNigoriHandler() is a workaround for coexistence with Directory
+  // implementation, returning nullptr here is expected behavior.
+  return nullptr;
+}
+
 bool NigoriSyncBridgeImpl::NeedKeystoreKey() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   // We explicitly ask the server for keystore keys iff it's first-time sync,
diff --git a/components/sync/nigori/nigori_sync_bridge_impl.h b/components/sync/nigori/nigori_sync_bridge_impl.h
index fcf14f1..d779999 100644
--- a/components/sync/nigori/nigori_sync_bridge_impl.h
+++ b/components/sync/nigori/nigori_sync_bridge_impl.h
@@ -53,6 +53,9 @@
   void EnableEncryptEverything() override;
   bool IsEncryptEverythingEnabled() const override;
   base::Time GetKeystoreMigrationTime() const override;
+  Cryptographer* GetCryptographerUnsafe() override;
+  KeystoreKeysHandler* GetKeystoreKeysHandler() override;
+  syncable::NigoriHandler* GetNigoriHandler() override;
 
   // KeystoreKeysHandler implementation.
   bool NeedKeystoreKey() const override;
diff --git a/components/sync/test/engine/test_directory_setter_upper.cc b/components/sync/test/engine/test_directory_setter_upper.cc
index a193683..138a6df 100644
--- a/components/sync/test/engine/test_directory_setter_upper.cc
+++ b/components/sync/test/engine/test_directory_setter_upper.cc
@@ -35,7 +35,7 @@
           name_, base::BindRepeating(
                      []() -> std::string { return "kTestCacheGuid"; })),
       MakeWeakHandle(handler_.GetWeakPtr()), base::Closure(),
-      &encryption_handler_, encryption_handler_.cryptographer());
+      &encryption_handler_, encryption_handler_.GetCryptographerUnsafe());
   ASSERT_EQ(syncable::OPENED_NEW,
             directory_->Open(name_, &delegate_, transaction_observer));
   directory_->set_cache_guid("kTestCacheGuid");
@@ -52,7 +52,7 @@
   directory_ = std::make_unique<syncable::Directory>(
       std::move(directory_store), MakeWeakHandle(handler_.GetWeakPtr()),
       base::Closure(), &encryption_handler_,
-      encryption_handler_.cryptographer());
+      encryption_handler_.GetCryptographerUnsafe());
   ASSERT_EQ(syncable::OPENED_EXISTING,
             directory_->Open(name_, &delegate_, transaction_observer));
   directory_->set_cache_guid("kTestCacheGuid");
diff --git a/components/sync/test/fake_sync_encryption_handler.cc b/components/sync/test/fake_sync_encryption_handler.cc
index 1a089dbd..e66e709 100644
--- a/components/sync/test/fake_sync_encryption_handler.cc
+++ b/components/sync/test/fake_sync_encryption_handler.cc
@@ -128,4 +128,16 @@
   return base::Time();
 }
 
+Cryptographer* FakeSyncEncryptionHandler::GetCryptographerUnsafe() {
+  return &cryptographer_;
+}
+
+KeystoreKeysHandler* FakeSyncEncryptionHandler::GetKeystoreKeysHandler() {
+  return this;
+}
+
+syncable::NigoriHandler* FakeSyncEncryptionHandler::GetNigoriHandler() {
+  return this;
+}
+
 }  // namespace syncer
diff --git a/components/sync/test/fake_sync_encryption_handler.h b/components/sync/test/fake_sync_encryption_handler.h
index 002b065..34869c1 100644
--- a/components/sync/test/fake_sync_encryption_handler.h
+++ b/components/sync/test/fake_sync_encryption_handler.h
@@ -43,6 +43,9 @@
   PassphraseType GetPassphraseType(
       syncable::BaseTransaction* const trans) const override;
   base::Time GetKeystoreMigrationTime() const override;
+  Cryptographer* GetCryptographerUnsafe() override;
+  KeystoreKeysHandler* GetKeystoreKeysHandler() override;
+  syncable::NigoriHandler* GetNigoriHandler() override;
 
   // NigoriHandler implemenation.
   void ApplyNigoriUpdate(const sync_pb::NigoriSpecifics& nigori,
@@ -57,8 +60,6 @@
   bool NeedKeystoreKey() const override;
   bool SetKeystoreKeys(const std::vector<std::string>& keys) override;
 
-  Cryptographer* cryptographer() { return &cryptographer_; }
-
  private:
   base::ObserverList<SyncEncryptionHandler::Observer>::Unchecked observers_;
   ModelTypeSet encrypted_types_;
diff --git a/components/sync_sessions/local_session_event_handler_impl_unittest.cc b/components/sync_sessions/local_session_event_handler_impl_unittest.cc
index 79fe3ed..b585262 100644
--- a/components/sync_sessions/local_session_event_handler_impl_unittest.cc
+++ b/components/sync_sessions/local_session_event_handler_impl_unittest.cc
@@ -596,7 +596,14 @@
   AddTab(kWindowId1, kBar1, kTabId2);
 }
 
-TEST_F(LocalSessionEventHandlerImplTest, PropagateClosedTab) {
+TEST_F(LocalSessionEventHandlerImplTest,
+       PropagateClosedTabWithoutDeferredRecyclingNorImmediateDeletion) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{kDeferRecyclingOfSyncTabNodesIfUnsynced,
+                             kTabNodePoolImmediateDeletion});
+
   AddWindow(kWindowId1);
   AddTab(kWindowId1, kFoo1, kTabId1);
   TestSyncedTabDelegate* tab2 = AddTab(kWindowId1, kBar1, kTabId2);
@@ -625,8 +632,7 @@
   handler_->OnLocalTabModified(tab2);
 }
 
-TEST_F(LocalSessionEventHandlerImplTest,
-       PropagateClosedTabWithDeferredRecyclingAndImmediateDeletion) {
+TEST_F(LocalSessionEventHandlerImplTest, PropagateClosedTab) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitWithFeatures(
       /*enabled_features=*/{kDeferRecyclingOfSyncTabNodesIfUnsynced,
diff --git a/components/sync_sessions/session_sync_bridge_unittest.cc b/components/sync_sessions/session_sync_bridge_unittest.cc
index 8d0bab38..f4ced44 100644
--- a/components/sync_sessions/session_sync_bridge_unittest.cc
+++ b/components/sync_sessions/session_sync_bridge_unittest.cc
@@ -879,9 +879,8 @@
                               /*tab_node_id=*/1, {"http://bar.com/"})))));
 }
 
-TEST_F(SessionSyncBridgeTest, ShouldRestoreLocalSessionWithFreedTab) {
-  const int kWindowId1 = 1000001;
-  const int kWindowId2 = 1000002;
+TEST_F(SessionSyncBridgeTest, ShouldRecycleTabNodeAfterCommitCompleted) {
+  const int kWindowId = 1000001;
   const int kTabId1 = 1000003;
   const int kTabId2 = 1000004;
   const int kTabId3 = 1000005;
@@ -889,6 +888,107 @@
   // Zero is the first assigned tab node ID.
   const int kTabNodeId1 = 0;
   const int kTabNodeId2 = 1;
+  const int kTabNodeId3 = 2;
+
+  AddWindow(kWindowId);
+  TestSyncedTabDelegate* tab1 = AddTab(kWindowId, "http://foo.com/", kTabId1);
+
+  const std::string header_storage_key =
+      SessionStore::GetHeaderStorageKey(kLocalSessionTag);
+  const std::string tab_storage_key1 =
+      SessionStore::GetTabStorageKey(kLocalSessionTag, kTabNodeId1);
+  const std::string tab_storage_key2 =
+      SessionStore::GetTabStorageKey(kLocalSessionTag, kTabNodeId2);
+  const std::string tab_storage_key3 =
+      SessionStore::GetTabStorageKey(kLocalSessionTag, kTabNodeId3);
+  const std::string tab_client_tag1 =
+      SessionStore::GetTabClientTagForTest(kLocalSessionTag, kTabNodeId1);
+  const std::string tab_client_tag2 =
+      SessionStore::GetTabClientTagForTest(kLocalSessionTag, kTabNodeId2);
+  const std::string tab_client_tag3 =
+      SessionStore::GetTabClientTagForTest(kLocalSessionTag, kTabNodeId3);
+
+  InitializeBridge();
+  StartSyncing();
+
+  // Mimic a commit completing for the initial sync.
+  ASSERT_TRUE(real_processor()->HasLocalChangesForTest());
+  sync_pb::ModelTypeState state;
+  state.set_initial_sync_done(true);
+  real_processor()->OnCommitCompleted(state,
+                                      {CreateSuccessResponse(kLocalSessionTag),
+                                       CreateSuccessResponse(tab_client_tag1)});
+  ASSERT_FALSE(real_processor()->HasLocalChangesForTest());
+
+  // Open a second tab.
+  AddTab(kWindowId, "http://bar.com/", kTabId2);
+  ASSERT_TRUE(real_processor()->HasLocalChangesForTest());
+
+  // Close |kTabId2| and force reassociation by navigating in the remaining open
+  // tab, leading to a freed tab entity. However, while there are pending
+  // changes to commit, the entity shouldn't be deleted (to prevent history
+  // loss).
+  EXPECT_CALL(mock_processor(), Delete(_, _)).Times(0);
+  CloseTab(kTabId2);
+  tab1->Navigate("http://foo2.com/");
+  EXPECT_TRUE(real_processor()->HasLocalChangesForTest());
+
+  EXPECT_THAT(
+      GetAllData(),
+      UnorderedElementsAre(
+          Pair(header_storage_key,
+               EntityDataHasSpecifics(
+                   MatchesHeader(kLocalSessionTag, {kWindowId}, {kTabId1}))),
+          Pair(tab_storage_key1,
+               EntityDataHasSpecifics(
+                   MatchesTab(kLocalSessionTag, kWindowId, kTabId1, kTabNodeId1,
+                              {"http://foo.com/", "http://foo2.com/"}))),
+          Pair(tab_storage_key2, EntityDataHasSpecifics(MatchesTab(
+                                     kLocalSessionTag, kWindowId, kTabId2,
+                                     kTabNodeId2, {"http://bar.com/"})))));
+
+  // If a new tab is opened, the entity with unsynced changes should not be
+  // recycled.
+  AddTab(kWindowId, "http://baz.com/", kTabId3);
+  EXPECT_THAT(GetAllData(), UnorderedElementsAre(Pair(header_storage_key, _),
+                                                 Pair(tab_storage_key1, _),
+                                                 Pair(tab_storage_key2, _),
+                                                 Pair(tab_storage_key3, _)));
+
+  // Completing the commit for the previously closed tab should issue a
+  // deletion. For that to trigger, we need to trigger the next association,
+  // which we do by navigating in one of the open tabs.
+  EXPECT_CALL(mock_processor(), Delete(tab_storage_key2, _));
+  real_processor()->OnCommitCompleted(state,
+                                      {CreateSuccessResponse(tab_client_tag2)});
+  tab1->Navigate("http://foo3.com/");
+  EXPECT_THAT(GetAllData(), UnorderedElementsAre(Pair(header_storage_key, _),
+                                                 Pair(tab_storage_key1, _),
+                                                 Pair(tab_storage_key3, _)));
+
+  // If yet anothertab is opened, the entity for the closed tab should be
+  // recycled.
+  AddTab(kWindowId, "http://qux.com/", kTabId4);
+  EXPECT_THAT(
+      GetAllData(),
+      UnorderedElementsAre(
+          Pair(header_storage_key, _), Pair(tab_storage_key1, _),
+          Pair(tab_storage_key2, EntityDataHasSpecifics(MatchesTab(
+                                     kLocalSessionTag, kWindowId, kTabId4,
+                                     kTabNodeId2, {"http://qux.com/"}))),
+          Pair(tab_storage_key3, _)));
+}
+
+TEST_F(SessionSyncBridgeTest, ShouldRestoreLocalSessionWithFreedTab) {
+  const int kWindowId1 = 1000001;
+  const int kWindowId2 = 1000002;
+  const int kTabId1 = 1000003;
+  const int kTabId2 = 1000004;
+  const int kTabId3 = 1000005;
+  // Zero is the first assigned tab node ID.
+  const int kTabNodeId1 = 0;
+  const int kTabNodeId2 = 1;
+  const int kTabNodeId3 = 2;
 
   AddWindow(kWindowId1);
   TestSyncedTabDelegate* tab1 = AddTab(kWindowId1, "http://foo.com/", kTabId1);
@@ -900,6 +1000,8 @@
       SessionStore::GetTabStorageKey(kLocalSessionTag, kTabNodeId1);
   const std::string tab_storage_key2 =
       SessionStore::GetTabStorageKey(kLocalSessionTag, kTabNodeId2);
+  const std::string tab_storage_key3 =
+      SessionStore::GetTabStorageKey(kLocalSessionTag, kTabNodeId3);
 
   InitializeBridge();
   StartSyncing();
@@ -911,7 +1013,7 @@
   // Close |kTabId2| and force reassociation by navigating in the remaining open
   // tab, leading to a freed tab entity.
   CloseTab(kTabId2);
-  tab1->Navigate("http://baz.com/");
+  tab1->Navigate("http://foo2.com/");
 
   ASSERT_THAT(GetData(header_storage_key),
               EntityDataHasSpecifics(
@@ -923,39 +1025,31 @@
   // The browser gets restarted with a new initial tab, for example because the
   // user chose "Continue where you left off".
   AddWindow(kWindowId2);
-  AddTab(kWindowId2, "http://qux.com/", kTabId3);
+  AddTab(kWindowId2, "http://baz.com/", kTabId3);
 
   // Start the bridge again.
   InitializeBridge();
   StartSyncing();
 
-  // One tab node de should be free at this point. In the current implementation
-  // (subject to change), this is |kTabNodeId1|. This is because |kTabId3| is
-  // assigned |kTabNodeId2|.
+  // Two tab nodes should be free at this point, because both tabs have been
+  // closed. However, they are also unsynced (the commit hasn't completed),
+  // which prevents their recycling, so a new tab node should be created.
   ASSERT_THAT(
       GetAllData(),
       UnorderedElementsAre(
           Pair(header_storage_key,
                EntityDataHasSpecifics(
                    MatchesHeader(kLocalSessionTag, {kWindowId2}, {kTabId3}))),
+          Pair(tab_storage_key1,
+               EntityDataHasSpecifics(MatchesTab(
+                   kLocalSessionTag, kWindowId1, kTabId1, kTabNodeId1,
+                   {"http://foo.com/", "http://foo2.com/"}))),
           Pair(tab_storage_key2, EntityDataHasSpecifics(MatchesTab(
+                                     kLocalSessionTag, kWindowId1, kTabId2,
+                                     kTabNodeId2, {"http://bar.com/"}))),
+          Pair(tab_storage_key3, EntityDataHasSpecifics(MatchesTab(
                                      kLocalSessionTag, kWindowId2, kTabId3,
-                                     kTabNodeId2, {"http://qux.com/"})))));
-
-  // When a new tab is opened (|kTabId4|), |kTabNodeId1| should be reused.
-  AddTab(kWindowId2, "http://quux.com/", kTabId4);
-  EXPECT_THAT(
-      GetAllData(),
-      UnorderedElementsAre(
-          Pair(header_storage_key,
-               EntityDataHasSpecifics(MatchesHeader(
-                   kLocalSessionTag, {kWindowId2}, {kTabId3, kTabId4}))),
-          Pair(tab_storage_key2, EntityDataHasSpecifics(MatchesTab(
-                                     kLocalSessionTag, kWindowId2, kTabId3,
-                                     kTabNodeId2, {"http://qux.com/"}))),
-          Pair(tab_storage_key1, EntityDataHasSpecifics(MatchesTab(
-                                     kLocalSessionTag, kWindowId2, kTabId4,
-                                     kTabNodeId1, {"http://quux.com/"})))));
+                                     kTabNodeId3, {"http://baz.com/"})))));
 }
 
 TEST_F(SessionSyncBridgeTest, ShouldDisableSyncAndReenable) {
diff --git a/components/sync_sessions/synced_session_tracker.cc b/components/sync_sessions/synced_session_tracker.cc
index e7cba48..ae74d64 100644
--- a/components/sync_sessions/synced_session_tracker.cc
+++ b/components/sync_sessions/synced_session_tracker.cc
@@ -16,8 +16,7 @@
 namespace sync_sessions {
 
 const base::Feature kDeferRecyclingOfSyncTabNodesIfUnsynced{
-    "DeferRecyclingOfSyncTabNodesIfUnsynced",
-    base::FEATURE_DISABLED_BY_DEFAULT};
+    "DeferRecyclingOfSyncTabNodesIfUnsynced", base::FEATURE_ENABLED_BY_DEFAULT};
 
 namespace {
 
@@ -544,7 +543,7 @@
   DCHECK(!local_session_tag_.empty());
   TrackedSession* session = GetTrackedSession(local_session_tag_);
   CleanupSessionImpl(local_session_tag_, is_tab_node_unsynced_cb);
-  return session->tab_node_pool.CleanupTabNodes();
+  return session->tab_node_pool.CleanupFreeTabNodes();
 }
 
 int SyncedSessionTracker::LookupTabNodeFromTabId(const std::string& session_tag,
diff --git a/components/sync_sessions/synced_session_tracker_unittest.cc b/components/sync_sessions/synced_session_tracker_unittest.cc
index eb97e15..8d8ea54 100644
--- a/components/sync_sessions/synced_session_tracker_unittest.cc
+++ b/components/sync_sessions/synced_session_tracker_unittest.cc
@@ -494,7 +494,13 @@
   ASSERT_TRUE(VerifyTabIntegrity(kTag));
 }
 
-TEST_F(SyncedSessionTrackerTest, CleanupLocalTabs) {
+TEST_F(SyncedSessionTrackerTest, CleanupLocalTabsWithoutDeferredRecycling) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{kDeferRecyclingOfSyncTabNodesIfUnsynced,
+                             kTabNodePoolImmediateDeletion});
+
   tracker_.InitLocalSession(kTag, kSessionName, kDeviceType);
 
   // Start with two restored tab nodes.
@@ -537,7 +543,7 @@
   ASSERT_TRUE(VerifyTabIntegrity(kTag));
 }
 
-TEST_F(SyncedSessionTrackerTest, CleanupLocalTabsWithDeferredRecycling) {
+TEST_F(SyncedSessionTrackerTest, CleanupLocalTabs) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeature(kDeferRecyclingOfSyncTabNodesIfUnsynced);
 
@@ -570,8 +576,8 @@
   // During cleanup, only two tabs should be freed:
   // - |kTabNode3| because of its age, although it's unsynced.
   // - |kTabNode4| because it's synced.
-  EXPECT_TRUE(
-      tracker_.CleanupLocalTabs(is_tab_node_unsynced_cb_.Get()).empty());
+  EXPECT_THAT(tracker_.CleanupLocalTabs(is_tab_node_unsynced_cb_.Get()),
+              ElementsAre(kTabNode3, kTabNode4));
   ASSERT_EQ(kTabNode1, tracker_.LookupTabNodeFromTabId(kTag, kTab1));
   EXPECT_EQ(kTabNode2, tracker_.LookupTabNodeFromTabId(kTag, kTab2));
   EXPECT_EQ(TabNodePool::kInvalidTabNodeID,
@@ -582,8 +588,8 @@
   // |kTabNode2| now becomes synced (commit succeeded), which means it should be
   // freed during cleanup.
   EXPECT_CALL(is_tab_node_unsynced_cb_, Run(kTabNode2)).WillOnce(Return(false));
-  EXPECT_TRUE(
-      tracker_.CleanupLocalTabs(is_tab_node_unsynced_cb_.Get()).empty());
+  EXPECT_THAT(tracker_.CleanupLocalTabs(is_tab_node_unsynced_cb_.Get()),
+              ElementsAre(kTabNode2));
   EXPECT_EQ(TabNodePool::kInvalidTabNodeID,
             tracker_.LookupTabNodeFromTabId(kTag, kTab2));
   EXPECT_EQ(kTabNode2, tracker_.AssociateLocalTabWithFreeTabNode(kTab5));
@@ -745,8 +751,7 @@
   EXPECT_TRUE(IsLocalTabNodeAssociated(kTabNode1));
   tracker_.PutWindowInSession(kTag, kWindow1);
   tracker_.PutTabInWindow(kTag, kWindow1, kTab2);
-  EXPECT_TRUE(
-      tracker_.CleanupLocalTabs(is_tab_node_unsynced_cb_.Get()).empty());
+  tracker_.CleanupLocalTabs(is_tab_node_unsynced_cb_.Get());
   ASSERT_TRUE(VerifyTabIntegrity(kTag));
   EXPECT_FALSE(tracker_.IsTabUnmappedForTesting(kTab1));
   EXPECT_FALSE(tracker_.IsTabUnmappedForTesting(kTab2));
diff --git a/components/sync_sessions/tab_node_pool.cc b/components/sync_sessions/tab_node_pool.cc
index 24f5be8..0eb692e 100644
--- a/components/sync_sessions/tab_node_pool.cc
+++ b/components/sync_sessions/tab_node_pool.cc
@@ -20,7 +20,7 @@
 const size_t TabNodePool::kFreeNodesHighWatermark = 100;
 
 const base::Feature kTabNodePoolImmediateDeletion{
-    "TabNodePoolImmediateDeletion", base::FEATURE_DISABLED_BY_DEFAULT};
+    "TabNodePoolImmediateDeletion", base::FEATURE_ENABLED_BY_DEFAULT};
 
 TabNodePool::TabNodePool() : max_used_tab_node_id_(kInvalidTabNodeID) {}
 
@@ -147,7 +147,7 @@
   return SessionID::InvalidValue();
 }
 
-std::set<int> TabNodePool::CleanupTabNodes() {
+std::set<int> TabNodePool::CleanupFreeTabNodes() {
   if (base::FeatureList::IsEnabled(kTabNodePoolImmediateDeletion)) {
     // Convert all free nodes into missing nodes, each representing a deletion.
     missing_nodes_pool_.insert(free_nodes_pool_.begin(),
diff --git a/components/sync_sessions/tab_node_pool.h b/components/sync_sessions/tab_node_pool.h
index c7655cb..01f6874 100644
--- a/components/sync_sessions/tab_node_pool.h
+++ b/components/sync_sessions/tab_node_pool.h
@@ -68,10 +68,8 @@
   // pool.
   void FreeTab(SessionID tab_id);
 
-  // Returns the IDs for any free nodes to be deleted as proscribed by the free
-  // node low/high watermarks, in order to ensure the free node pool does not
-  // grow too large.
-  std::set<int> CleanupTabNodes();
+  // Deletes all free tab nodes. Returns the IDs of the deleted nodes.
+  std::set<int> CleanupFreeTabNodes();
 
   // Deletes all known mappings for |tab_node_id|. As opposed to FreeTab(), it
   // does NOT free the node for later reuse. This is used for foreign sessions
diff --git a/components/sync_sessions/tab_node_pool_unittest.cc b/components/sync_sessions/tab_node_pool_unittest.cc
index f2bface8..b2eb15ca 100644
--- a/components/sync_sessions/tab_node_pool_unittest.cc
+++ b/components/sync_sessions/tab_node_pool_unittest.cc
@@ -19,7 +19,6 @@
 
 const int kTabNodeId1 = 10;
 const int kTabNodeId2 = 5;
-const int kTabNodeId3 = 30;
 const SessionID kTabId1 = SessionID::FromSerializedValue(1010);
 const SessionID kTabId2 = SessionID::FromSerializedValue(1020);
 const SessionID kTabId3 = SessionID::FromSerializedValue(1030);
@@ -44,31 +43,48 @@
   TabNodePool pool_;
 };
 
-TEST_F(SyncTabNodePoolTest, TabNodeIdIncreases) {
-  // max_used_tab_node_ always increases.
-  pool_.ReassociateTabNode(kTabNodeId1, kTabId1);
-  EXPECT_EQ(kTabNodeId1, GetMaxUsedTabNodeId());
-  pool_.ReassociateTabNode(kTabNodeId2, kTabId2);
-  EXPECT_EQ(kTabNodeId1, GetMaxUsedTabNodeId());
-  pool_.ReassociateTabNode(kTabNodeId3, kTabId3);
-  EXPECT_EQ(kTabNodeId3, GetMaxUsedTabNodeId());
-  // Freeing a tab node does not change max_used_tab_node_id_.
+TEST_F(SyncTabNodePoolTest, MaxTabNodeIdShouldIncrease) {
+  EXPECT_EQ(-1, GetMaxUsedTabNodeId());
+  pool_.ReassociateTabNode(10, kTabId1);
+  EXPECT_EQ(10, GetMaxUsedTabNodeId());
+  pool_.ReassociateTabNode(5, kTabId2);
+  EXPECT_EQ(10, GetMaxUsedTabNodeId());
+  pool_.ReassociateTabNode(20, kTabId3);
+  EXPECT_EQ(20, GetMaxUsedTabNodeId());
+}
+
+TEST_F(SyncTabNodePoolTest, MaxTabNodeIdShouldDecrease) {
+  pool_.ReassociateTabNode(10, kTabId1);
+  pool_.ReassociateTabNode(5, kTabId2);
+  pool_.ReassociateTabNode(20, kTabId3);
+  EXPECT_EQ(20, GetMaxUsedTabNodeId());
+
   pool_.FreeTab(kTabId3);
-  pool_.CleanupTabNodes();
-  pool_.FreeTab(kTabId2);
-  EXPECT_THAT(pool_.CleanupTabNodes(), IsEmpty());
+  ASSERT_THAT(pool_.CleanupFreeTabNodes(), ElementsAre(20));
+  EXPECT_EQ(10, GetMaxUsedTabNodeId());
+
   pool_.FreeTab(kTabId1);
-  EXPECT_THAT(pool_.CleanupTabNodes(), IsEmpty());
-  for (int i = 0; i < 3; ++i) {
-    const SessionID tab_id = SessionID::FromSerializedValue(i + 1);
-    ASSERT_EQ(TabNodePool::kInvalidTabNodeID,
-              pool_.GetTabNodeIdFromTabId(tab_id));
-    EXPECT_NE(TabNodePool::kInvalidTabNodeID,
-              pool_.AssociateWithFreeTabNode(tab_id));
-    EXPECT_EQ(kTabNodeId3, GetMaxUsedTabNodeId());
-  }
-  EXPECT_THAT(pool_.CleanupTabNodes(), IsEmpty());
-  EXPECT_EQ(kTabNodeId3, GetMaxUsedTabNodeId());
+  ASSERT_THAT(pool_.CleanupFreeTabNodes(), ElementsAre(10));
+  EXPECT_EQ(5, GetMaxUsedTabNodeId());
+
+  pool_.FreeTab(kTabId2);
+  ASSERT_THAT(pool_.CleanupFreeTabNodes(), ElementsAre(5));
+  EXPECT_EQ(-1, GetMaxUsedTabNodeId());
+}
+
+TEST_F(SyncTabNodePoolTest, MaxTabNodeIdShouldNotChange) {
+  pool_.ReassociateTabNode(10, kTabId1);
+  pool_.ReassociateTabNode(5, kTabId2);
+  pool_.ReassociateTabNode(20, kTabId3);
+  EXPECT_EQ(20, GetMaxUsedTabNodeId());
+
+  pool_.FreeTab(kTabId1);
+  ASSERT_THAT(pool_.CleanupFreeTabNodes(), ElementsAre(10));
+  EXPECT_EQ(20, GetMaxUsedTabNodeId());
+
+  pool_.FreeTab(kTabId2);
+  ASSERT_THAT(pool_.CleanupFreeTabNodes(), ElementsAre(5));
+  EXPECT_EQ(20, GetMaxUsedTabNodeId());
 }
 
 TEST_F(SyncTabNodePoolTest, Reassociation) {
@@ -136,7 +152,10 @@
   EXPECT_EQ(0, pool_.AssociateWithFreeTabNode(kTabId3));
 }
 
-TEST_F(SyncTabNodePoolTest, TabPoolFreeNodeLimits) {
+TEST_F(SyncTabNodePoolTest, TabPoolFreeNodeWatermarkLimits) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndDisableFeature(kTabNodePoolImmediateDeletion);
+
   // Allocate TabNodePool::kFreeNodesHighWatermark + 1 nodes and verify that
   // freeing the last node reduces the free node pool size to
   // kFreeNodesLowWatermark.
@@ -151,17 +170,16 @@
 
   for (size_t i = 1; i <= used_sync_ids.size(); ++i) {
     pool_.FreeTab(SessionID::FromSerializedValue(i));
-    EXPECT_THAT(pool_.CleanupTabNodes(), IsEmpty());
+    EXPECT_THAT(pool_.CleanupFreeTabNodes(), IsEmpty());
   }
 
   // Freeing the last sync node should drop the free nodes to
   // kFreeNodesLowWatermark.
   pool_.FreeTab(
       SessionID::FromSerializedValue(TabNodePool::kFreeNodesHighWatermark + 1));
-  std::set<int> deleted_node_ids = pool_.CleanupTabNodes();
-  EXPECT_EQ(TabNodePool::kFreeNodesHighWatermark + 1 -
-                TabNodePool::kFreeNodesLowWatermark,
-            deleted_node_ids.size());
+  std::set<int> deleted_node_ids = pool_.CleanupFreeTabNodes();
+  EXPECT_EQ(deleted_node_ids.size(), TabNodePool::kFreeNodesHighWatermark + 1 -
+                                         TabNodePool::kFreeNodesLowWatermark);
   // Make sure the highest ones are deleted.
   EXPECT_EQ(0U,
             deleted_node_ids.count(TabNodePool::kFreeNodesLowWatermark - 1));
@@ -191,42 +209,33 @@
   EXPECT_EQ(2, pool_.AssociateWithFreeTabNode(kTabId5));
 }
 
-TEST_F(SyncTabNodePoolTest, AggressiveCleanupTabNodesMiddle) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeature(kTabNodePoolImmediateDeletion);
-
+TEST_F(SyncTabNodePoolTest, AggressiveCleanupFreeTabNodesMiddle) {
   pool_.ReassociateTabNode(/*tab_node_id=*/0, kTabId1);
   pool_.ReassociateTabNode(/*tab_node_id=*/1, kTabId2);
   pool_.ReassociateTabNode(/*tab_node_id=*/2, kTabId3);
 
   pool_.FreeTab(kTabId2);
 
-  EXPECT_THAT(pool_.CleanupTabNodes(), UnorderedElementsAre(1));
+  EXPECT_THAT(pool_.CleanupFreeTabNodes(), ElementsAre(1));
   EXPECT_EQ(2, GetMaxUsedTabNodeId());
   EXPECT_EQ(1, pool_.AssociateWithFreeTabNode(kTabId4));
   EXPECT_EQ(3, pool_.AssociateWithFreeTabNode(kTabId5));
 }
 
-TEST_F(SyncTabNodePoolTest, AggressiveCleanupTabNodesMax) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeature(kTabNodePoolImmediateDeletion);
-
+TEST_F(SyncTabNodePoolTest, AggressiveCleanupFreeTabNodesMax) {
   pool_.ReassociateTabNode(/*tab_node_id=*/0, kTabId1);
   pool_.ReassociateTabNode(/*tab_node_id=*/1, kTabId2);
   pool_.ReassociateTabNode(/*tab_node_id=*/2, kTabId3);
 
   pool_.FreeTab(kTabId3);
 
-  EXPECT_THAT(pool_.CleanupTabNodes(), UnorderedElementsAre(2));
+  EXPECT_THAT(pool_.CleanupFreeTabNodes(), ElementsAre(2));
   EXPECT_EQ(1, GetMaxUsedTabNodeId());
   EXPECT_EQ(2, pool_.AssociateWithFreeTabNode(kTabId4));
   EXPECT_EQ(3, pool_.AssociateWithFreeTabNode(kTabId5));
 }
 
-TEST_F(SyncTabNodePoolTest, AggressiveCleanupTabNodesMultiple) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeature(kTabNodePoolImmediateDeletion);
-
+TEST_F(SyncTabNodePoolTest, AggressiveCleanupFreeTabNodesMultiple) {
   pool_.ReassociateTabNode(/*tab_node_id=*/0, kTabId1);
   pool_.ReassociateTabNode(/*tab_node_id=*/1, kTabId2);
   pool_.ReassociateTabNode(/*tab_node_id=*/2, kTabId3);
@@ -234,22 +243,19 @@
   pool_.FreeTab(kTabId1);
   pool_.FreeTab(kTabId2);
 
-  EXPECT_THAT(pool_.CleanupTabNodes(), UnorderedElementsAre(0, 1));
+  EXPECT_THAT(pool_.CleanupFreeTabNodes(), UnorderedElementsAre(0, 1));
   EXPECT_EQ(2, GetMaxUsedTabNodeId());
   EXPECT_EQ(0, pool_.AssociateWithFreeTabNode(kTabId4));
   EXPECT_EQ(1, pool_.AssociateWithFreeTabNode(kTabId5));
   EXPECT_EQ(3, pool_.AssociateWithFreeTabNode(kTabId6));
 }
 
-TEST_F(SyncTabNodePoolTest, AggressiveCleanupTabNodesAll) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeature(kTabNodePoolImmediateDeletion);
-
+TEST_F(SyncTabNodePoolTest, AggressiveCleanupFreeTabNodesAll) {
   pool_.ReassociateTabNode(/*tab_node_id=*/0, kTabId1);
 
   pool_.FreeTab(kTabId1);
 
-  EXPECT_THAT(pool_.CleanupTabNodes(), UnorderedElementsAre(0));
+  EXPECT_THAT(pool_.CleanupFreeTabNodes(), ElementsAre(0));
   EXPECT_EQ(-1, GetMaxUsedTabNodeId());
   EXPECT_EQ(0, pool_.AssociateWithFreeTabNode(kTabId4));
 }
diff --git a/components/tracing/test/trace_event_perftest.cc b/components/tracing/test/trace_event_perftest.cc
index 2cd7a03..684a8d2 100644
--- a/components/tracing/test/trace_event_perftest.cc
+++ b/components/tracing/test/trace_event_perftest.cc
@@ -7,9 +7,9 @@
 #include "base/bind.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/ref_counted_memory.h"
-#include "base/message_loop/message_loop.h"
 #include "base/pending_task.h"
 #include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
 #include "base/threading/thread.h"
 #include "base/trace_event/trace_event.h"
 #include "base/trace_event/traced_value.h"
@@ -90,7 +90,7 @@
   }
 
  private:
-  base::MessageLoop _message_loop;
+  base::test::ScopedTaskEnvironment scoped_task_environment;
 };
 
 TEST_F(TraceEventPerfTest, Submit_10000_TRACE_EVENT0) {
diff --git a/components/viz/client/shared_bitmap_reporter.h b/components/viz/client/shared_bitmap_reporter.h
index b2e1549..ca9bfaa 100644
--- a/components/viz/client/shared_bitmap_reporter.h
+++ b/components/viz/client/shared_bitmap_reporter.h
@@ -5,9 +5,9 @@
 #ifndef COMPONENTS_VIZ_CLIENT_SHARED_BITMAP_REPORTER_H_
 #define COMPONENTS_VIZ_CLIENT_SHARED_BITMAP_REPORTER_H_
 
+#include "base/memory/read_only_shared_memory_region.h"
 #include "components/viz/client/viz_client_export.h"
 #include "components/viz/common/resources/shared_bitmap.h"
-#include "mojo/public/cpp/system/buffer.h"
 
 namespace viz {
 
@@ -19,7 +19,7 @@
 class VIZ_CLIENT_EXPORT SharedBitmapReporter {
  public:
   // Associates a SharedBitmapId with a shared buffer handle.
-  virtual void DidAllocateSharedBitmap(mojo::ScopedSharedBufferHandle buffer,
+  virtual void DidAllocateSharedBitmap(base::ReadOnlySharedMemoryRegion region,
                                        const SharedBitmapId& id) = 0;
 
   // Disassociates a SharedBitmapId previously passed to
diff --git a/components/viz/common/resources/bitmap_allocation.cc b/components/viz/common/resources/bitmap_allocation.cc
index ede0e1f3..fd5d22f 100644
--- a/components/viz/common/resources/bitmap_allocation.cc
+++ b/components/viz/common/resources/bitmap_allocation.cc
@@ -62,16 +62,6 @@
   return shm;
 }
 
-mojo::ScopedSharedBufferHandle ToMojoHandle(
-    base::ReadOnlySharedMemoryRegion region) {
-  return mojo::WrapReadOnlySharedMemoryRegion(std::move(region));
-}
-
-base::ReadOnlySharedMemoryRegion FromMojoHandle(
-    mojo::ScopedSharedBufferHandle handle) {
-  return mojo::UnwrapReadOnlySharedMemoryRegion(std::move(handle));
-}
-
 }  // namespace bitmap_allocation
 
 }  // namespace viz
diff --git a/components/viz/common/resources/bitmap_allocation.h b/components/viz/common/resources/bitmap_allocation.h
index 8900a16..8068a193 100644
--- a/components/viz/common/resources/bitmap_allocation.h
+++ b/components/viz/common/resources/bitmap_allocation.h
@@ -26,22 +26,6 @@
     const gfx::Size& size,
     ResourceFormat format);
 
-// Converts a base::ReadOnlySharedMemoryRegion to its corresponding
-// Mojo scoped handle. This simply calls mojo::WrapReadOnlySharedMemoryRegion()
-// but allows the caller to not include the corresponding header where it is
-// defined. Moreover, it will be easy to grep for all uses of this method
-// in the future when MojoHandles will not longer be necessary.
-// TODO(crbug.com/951391): Remove once refactor is completed.
-VIZ_COMMON_EXPORT mojo::ScopedSharedBufferHandle ToMojoHandle(
-    base::ReadOnlySharedMemoryRegion region);
-
-// Converts a scoped Mojo handle back to a base::ReadOnlySharedMemoryRegion
-// This simply calls mojo::UnwrapReadOnlySharedMemoryRegion(), but has the same
-// benefits as ToMojoHandle() described above.
-// TODO(crbug.com/951391): Remove once refactor is completed.
-VIZ_COMMON_EXPORT base::ReadOnlySharedMemoryRegion FromMojoHandle(
-    mojo::ScopedSharedBufferHandle handle);
-
 }  // namespace bitmap_allocation
 
 }  // namespace viz
diff --git a/components/viz/service/compositor_frame_fuzzer/fuzzer_browser_process.cc b/components/viz/service/compositor_frame_fuzzer/fuzzer_browser_process.cc
index d084b0b4..6c9e745b 100644
--- a/components/viz/service/compositor_frame_fuzzer/fuzzer_browser_process.cc
+++ b/components/viz/service/compositor_frame_fuzzer/fuzzer_browser_process.cc
@@ -58,10 +58,8 @@
                                                 sink_client.BindInterfacePtr());
 
   for (auto& fuzzed_bitmap : allocated_bitmaps) {
-    sink_ptr->DidAllocateSharedBitmap(
-        bitmap_allocation::ToMojoHandle(
-            fuzzed_bitmap.shared_region.Duplicate()),
-        fuzzed_bitmap.id);
+    sink_ptr->DidAllocateSharedBitmap(fuzzed_bitmap.shared_region.Duplicate(),
+                                      fuzzed_bitmap.id);
   }
 
   lsi_allocator_.GenerateId();
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_impl.cc b/components/viz/service/frame_sinks/compositor_frame_sink_impl.cc
index c9d6235..41754d2 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_impl.cc
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_impl.cc
@@ -88,9 +88,9 @@
 }
 
 void CompositorFrameSinkImpl::DidAllocateSharedBitmap(
-    mojo::ScopedSharedBufferHandle buffer,
+    base::ReadOnlySharedMemoryRegion region,
     const SharedBitmapId& id) {
-  if (!support_->DidAllocateSharedBitmap(std::move(buffer), id)) {
+  if (!support_->DidAllocateSharedBitmap(std::move(region), id)) {
     DLOG(ERROR) << "DidAllocateSharedBitmap failed for duplicate "
                 << "SharedBitmapId";
     compositor_frame_sink_binding_.Close();
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_impl.h b/components/viz/service/frame_sinks/compositor_frame_sink_impl.h
index b05aaea..8eb02da 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_impl.h
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_impl.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_VIZ_SERVICE_FRAME_SINKS_COMPOSITOR_FRAME_SINK_IMPL_H_
 
 #include "base/macros.h"
+#include "base/memory/read_only_shared_memory_region.h"
 #include "components/viz/common/surfaces/frame_sink_id.h"
 #include "components/viz/common/surfaces/local_surface_id.h"
 #include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
@@ -42,7 +43,7 @@
       uint64_t submit_time,
       SubmitCompositorFrameSyncCallback callback) override;
   void DidNotProduceFrame(const BeginFrameAck& begin_frame_ack) override;
-  void DidAllocateSharedBitmap(mojo::ScopedSharedBufferHandle buffer,
+  void DidAllocateSharedBitmap(base::ReadOnlySharedMemoryRegion region,
                                const SharedBitmapId& id) override;
   void DidDeleteSharedBitmap(const SharedBitmapId& id) override;
 
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support.cc b/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
index 324e69a9..8f028bb2 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
@@ -334,10 +334,10 @@
 }
 
 bool CompositorFrameSinkSupport::DidAllocateSharedBitmap(
-    mojo::ScopedSharedBufferHandle buffer,
+    base::ReadOnlySharedMemoryRegion region,
     const SharedBitmapId& id) {
   if (!frame_sink_manager_->shared_bitmap_manager()->ChildAllocatedSharedBitmap(
-          bitmap_allocation::FromMojoHandle(std::move(buffer)).Map(), id)) {
+          region.Map(), id)) {
     return false;
   }
 
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support.h b/components/viz/service/frame_sinks/compositor_frame_sink_support.h
index a6b5fd83..723b061 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support.h
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.h
@@ -10,6 +10,7 @@
 
 #include "base/callback.h"
 #include "base/compiler_specific.h"
+#include "base/memory/read_only_shared_memory_region.h"
 #include "base/memory/weak_ptr.h"
 #include "base/optional.h"
 #include "base/time/time.h"
@@ -143,7 +144,7 @@
       base::Optional<HitTestRegionList> hit_test_region_list = base::nullopt,
       uint64_t submit_time = 0);
   // Returns false if the notification was not valid (a duplicate).
-  bool DidAllocateSharedBitmap(mojo::ScopedSharedBufferHandle buffer,
+  bool DidAllocateSharedBitmap(base::ReadOnlySharedMemoryRegion region,
                                const SharedBitmapId& id);
   void DidDeleteSharedBitmap(const SharedBitmapId& id);
 
diff --git a/components/viz/service/frame_sinks/direct_layer_tree_frame_sink.cc b/components/viz/service/frame_sinks/direct_layer_tree_frame_sink.cc
index 7529d22..bc7ad62f 100644
--- a/components/viz/service/frame_sinks/direct_layer_tree_frame_sink.cc
+++ b/components/viz/service/frame_sinks/direct_layer_tree_frame_sink.cc
@@ -185,9 +185,9 @@
 }
 
 void DirectLayerTreeFrameSink::DidAllocateSharedBitmap(
-    mojo::ScopedSharedBufferHandle buffer,
+    base::ReadOnlySharedMemoryRegion region,
     const SharedBitmapId& id) {
-  bool ok = support_->DidAllocateSharedBitmap(std::move(buffer), id);
+  bool ok = support_->DidAllocateSharedBitmap(std::move(region), id);
   DCHECK(ok);
 }
 
diff --git a/components/viz/service/frame_sinks/direct_layer_tree_frame_sink.h b/components/viz/service/frame_sinks/direct_layer_tree_frame_sink.h
index a8a61f0..15ae36b 100644
--- a/components/viz/service/frame_sinks/direct_layer_tree_frame_sink.h
+++ b/components/viz/service/frame_sinks/direct_layer_tree_frame_sink.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_VIZ_SERVICE_FRAME_SINKS_DIRECT_LAYER_TREE_FRAME_SINK_H_
 
 #include "base/macros.h"
+#include "base/memory/read_only_shared_memory_region.h"
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_checker.h"
 #include "cc/trees/layer_tree_frame_sink.h"
@@ -73,7 +74,7 @@
                              bool hit_test_data_changed,
                              bool show_hit_test_borders) override;
   void DidNotProduceFrame(const BeginFrameAck& ack) override;
-  void DidAllocateSharedBitmap(mojo::ScopedSharedBufferHandle buffer,
+  void DidAllocateSharedBitmap(base::ReadOnlySharedMemoryRegion region,
                                const SharedBitmapId& id) override;
   void DidDeleteSharedBitmap(const SharedBitmapId& id) override;
 
diff --git a/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc b/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
index 89ee7f6..b58ef60 100644
--- a/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
+++ b/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
@@ -274,9 +274,9 @@
 }
 
 void RootCompositorFrameSinkImpl::DidAllocateSharedBitmap(
-    mojo::ScopedSharedBufferHandle buffer,
+    base::ReadOnlySharedMemoryRegion region,
     const SharedBitmapId& id) {
-  if (!support_->DidAllocateSharedBitmap(std::move(buffer), id)) {
+  if (!support_->DidAllocateSharedBitmap(std::move(region), id)) {
     DLOG(ERROR) << "DidAllocateSharedBitmap failed for duplicate "
                 << "SharedBitmapId";
     compositor_frame_sink_binding_.Close();
diff --git a/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.h b/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.h
index e21d989..ba90476 100644
--- a/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.h
+++ b/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "base/macros.h"
+#include "base/memory/read_only_shared_memory_region.h"
 #include "build/build_config.h"
 #include "components/viz/common/surfaces/frame_sink_id.h"
 #include "components/viz/common/surfaces/local_surface_id.h"
@@ -73,7 +74,7 @@
       base::Optional<HitTestRegionList> hit_test_region_list,
       uint64_t submit_time) override;
   void DidNotProduceFrame(const BeginFrameAck& begin_frame_ack) override;
-  void DidAllocateSharedBitmap(mojo::ScopedSharedBufferHandle buffer,
+  void DidAllocateSharedBitmap(base::ReadOnlySharedMemoryRegion region,
                                const SharedBitmapId& id) override;
   void DidDeleteSharedBitmap(const SharedBitmapId& id) override;
   void SubmitCompositorFrameSync(
diff --git a/content/app_shim_remote_cocoa/render_widget_host_view_cocoa.mm b/content/app_shim_remote_cocoa/render_widget_host_view_cocoa.mm
index 188fe917..03c722b 100644
--- a/content/app_shim_remote_cocoa/render_widget_host_view_cocoa.mm
+++ b/content/app_shim_remote_cocoa/render_widget_host_view_cocoa.mm
@@ -680,7 +680,7 @@
   // to exit fullscreen and we don't want to prevent them from exiting.
   ui::DomCode domCode = ui::KeycodeConverter::NativeKeycodeToDomCode(keyCode);
   return keyboardLockActive_ && domCode != ui::DomCode::ESCAPE &&
-         (!lockedKeys_ || base::ContainsKey(lockedKeys_.value(), domCode));
+         (!lockedKeys_ || base::Contains(lockedKeys_.value(), domCode));
 }
 
 - (BOOL)performKeyEquivalent:(NSEvent*)theEvent {
diff --git a/content/browser/accessibility/accessibility_tree_formatter_auralinux.cc b/content/browser/accessibility/accessibility_tree_formatter_auralinux.cc
index 3583821..c9e23d0 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_auralinux.cc
+++ b/content/browser/accessibility/accessibility_tree_formatter_auralinux.cc
@@ -190,13 +190,313 @@
   dict->Set(kChildrenDictAttr, std::move(children));
 }
 
+// TODO(aleventhal) Remove this and use atk_role_get_name() once the following
+// GNOME bug is fixed: https://bugzilla.gnome.org/show_bug.cgi?id=795983
+const char* const kRoleNames[] = {
+    "invalid",  // ATK_ROLE_INVALID.
+    "accelerator label",
+    "alert",
+    "animation",
+    "arrow",
+    "calendar",
+    "canvas",
+    "check box",
+    "check menu item",
+    "color chooser",
+    "column header",
+    "combo box",
+    "dateeditor",
+    "desktop icon",
+    "desktop frame",
+    "dial",
+    "dialog",
+    "directory pane",
+    "drawing area",
+    "file chooser",
+    "filler",
+    "fontchooser",
+    "frame",
+    "glass pane",
+    "html container",
+    "icon",
+    "image",
+    "internal frame",
+    "label",
+    "layered pane",
+    "list",
+    "list item",
+    "menu",
+    "menu bar",
+    "menu item",
+    "option pane",
+    "page tab",
+    "page tab list",
+    "panel",
+    "password text",
+    "popup menu",
+    "progress bar",
+    "push button",
+    "radio button",
+    "radio menu item",
+    "root pane",
+    "row header",
+    "scroll bar",
+    "scroll pane",
+    "separator",
+    "slider",
+    "split pane",
+    "spin button",
+    "statusbar",
+    "table",
+    "table cell",
+    "table column header",
+    "table row header",
+    "tear off menu item",
+    "terminal",
+    "text",
+    "toggle button",
+    "tool bar",
+    "tool tip",
+    "tree",
+    "tree table",
+    "unknown",
+    "viewport",
+    "window",
+    "header",
+    "footer",
+    "paragraph",
+    "ruler",
+    "application",
+    "autocomplete",
+    "edit bar",
+    "embedded component",
+    "entry",
+    "chart",
+    "caption",
+    "document frame",
+    "heading",
+    "page",
+    "section",
+    "redundant object",
+    "form",
+    "link",
+    "input method window",
+    "table row",
+    "tree item",
+    "document spreadsheet",
+    "document presentation",
+    "document text",
+    "document web",
+    "document email",
+    "comment",
+    "list box",
+    "grouping",
+    "image map",
+    "notification",
+    "info bar",
+    "level bar",
+    "title bar",
+    "block quote",
+    "audio",
+    "video",
+    "definition",
+    "article",
+    "landmark",
+    "log",
+    "marquee",
+    "math",
+    "rating",
+    "timer",
+    "description list",
+    "description term",
+    "description value",
+    "static",
+    "math fraction",
+    "math root",
+    "subscript",
+    "superscript",
+    "footnote",  // ATK_ROLE_FOOTNOTE = 122.
+};
+
 void AccessibilityTreeFormatterAuraLinux::AddProperties(
     const BrowserAccessibility& node,
     base::DictionaryValue* dict) {
   dict->SetInteger("id", node.GetId());
   BrowserAccessibilityAuraLinux* acc_obj =
       ToBrowserAccessibilityAuraLinux(const_cast<BrowserAccessibility*>(&node));
-  acc_obj->GetNode()->AddAccessibilityTreeProperties(dict);
+  DCHECK(acc_obj);
+
+  ui::AXPlatformNodeAuraLinux* ax_platform_node = acc_obj->GetNode();
+  DCHECK(ax_platform_node);
+
+  AtkObject* atk_object = ax_platform_node->GetNativeViewAccessible();
+  DCHECK(atk_object);
+
+  AtkRole role = atk_object_get_role(atk_object);
+  if (role != ATK_ROLE_UNKNOWN) {
+    int role_index = static_cast<int>(role);
+    dict->SetString("role", kRoleNames[role_index]);
+  }
+
+  const gchar* name = atk_object_get_name(atk_object);
+  if (name)
+    dict->SetString("name", std::string(name));
+  const gchar* description = atk_object_get_description(atk_object);
+  if (description)
+    dict->SetString("description", std::string(description));
+
+  AtkStateSet* state_set = atk_object_ref_state_set(atk_object);
+  auto states = std::make_unique<base::ListValue>();
+  for (int i = ATK_STATE_INVALID; i < ATK_STATE_LAST_DEFINED; i++) {
+    AtkStateType state_type = static_cast<AtkStateType>(i);
+    if (atk_state_set_contains_state(state_set, state_type))
+      states->AppendString(atk_state_type_get_name(state_type));
+  }
+  dict->Set("states", std::move(states));
+
+  AtkRelationSet* relation_set = atk_object_ref_relation_set(atk_object);
+  auto relations = std::make_unique<base::ListValue>();
+  for (int i = ATK_RELATION_NULL; i < ATK_RELATION_LAST_DEFINED; i++) {
+    AtkRelationType relation_type = static_cast<AtkRelationType>(i);
+    if (atk_relation_set_contains(relation_set, relation_type))
+      relations->AppendString(atk_relation_type_get_name(relation_type));
+  }
+  dict->Set("relations", std::move(relations));
+
+  AtkAttributeSet* attributes = atk_object_get_attributes(atk_object);
+  for (AtkAttributeSet* attr = attributes; attr; attr = attr->next) {
+    AtkAttribute* attribute = static_cast<AtkAttribute*>(attr->data);
+    dict->SetString(attribute->name, attribute->value);
+  }
+  atk_attribute_set_free(attributes);
+
+  // Properties obtained via AtkValue.
+  auto value_properties = std::make_unique<base::ListValue>();
+  if (ATK_IS_VALUE(atk_object)) {
+    AtkValue* value = ATK_VALUE(atk_object);
+    GValue current = G_VALUE_INIT;
+    g_value_init(&current, G_TYPE_FLOAT);
+    atk_value_get_current_value(value, &current);
+    value_properties->AppendString(
+        base::StringPrintf("current=%f", g_value_get_float(&current)));
+
+    GValue minimum = G_VALUE_INIT;
+    g_value_init(&minimum, G_TYPE_FLOAT);
+    atk_value_get_minimum_value(value, &minimum);
+    value_properties->AppendString(
+        base::StringPrintf("minimum=%f", g_value_get_float(&minimum)));
+
+    GValue maximum = G_VALUE_INIT;
+    g_value_init(&maximum, G_TYPE_FLOAT);
+    atk_value_get_maximum_value(value, &maximum);
+    value_properties->AppendString(
+        base::StringPrintf("maximum=%f", g_value_get_float(&maximum)));
+  }
+  dict->Set("value", std::move(value_properties));
+
+  // Properties obtained via AtkTable.
+  auto table_properties = std::make_unique<base::ListValue>();
+  if (ATK_IS_TABLE(atk_object)) {
+    AtkTable* table = ATK_TABLE(atk_object);
+
+    // Column details.
+    int n_cols = atk_table_get_n_columns(table);
+    table_properties->AppendString(base::StringPrintf("cols=%i", n_cols));
+
+    std::vector<std::string> col_headers;
+    for (int i = 0; i < n_cols; i++) {
+      std::string header = atk_table_get_column_description(table, i);
+      if (!header.empty())
+        col_headers.push_back(base::StringPrintf("'%s'", header.c_str()));
+    }
+
+    if (!col_headers.size())
+      col_headers.push_back("NONE");
+
+    table_properties->AppendString(base::StringPrintf(
+        "headers=(%s);", base::JoinString(col_headers, ", ").c_str()));
+
+    // Row details.
+    int n_rows = atk_table_get_n_rows(table);
+    table_properties->AppendString(base::StringPrintf("rows=%i", n_rows));
+
+    std::vector<std::string> row_headers;
+    for (int i = 0; i < n_rows; i++) {
+      std::string header = atk_table_get_row_description(table, i);
+      if (!header.empty())
+        row_headers.push_back(base::StringPrintf("'%s'", header.c_str()));
+    }
+
+    if (!row_headers.size())
+      row_headers.push_back("NONE");
+
+    table_properties->AppendString(base::StringPrintf(
+        "headers=(%s);", base::JoinString(row_headers, ", ").c_str()));
+
+    // Caption details.
+    AtkObject* caption = atk_table_get_caption(table);
+    table_properties->AppendString(
+        base::StringPrintf("caption=%s;", caption ? "true" : "false"));
+
+    // Summarize information about the cells from the table's perspective here.
+    std::vector<std::string> span_info;
+    for (int r = 0; r < n_rows; r++) {
+      for (int c = 0; c < n_cols; c++) {
+        int row_span = atk_table_get_row_extent_at(table, r, c);
+        int col_span = atk_table_get_column_extent_at(table, r, c);
+        if (row_span != 1 || col_span != 1) {
+          span_info.push_back(base::StringPrintf("cell at %i,%i: %ix%i", r, c,
+                                                 row_span, col_span));
+        }
+      }
+    }
+    if (!span_info.size())
+      span_info.push_back("all: 1x1");
+
+    table_properties->AppendString(base::StringPrintf(
+        "spans=(%s)", base::JoinString(span_info, ", ").c_str()));
+  }
+
+  dict->Set("table", std::move(table_properties));
+
+  // Properties obtained via AtkTableCell, if possible. If we do not have at
+  // least ATK 2.12, use the same logic in our AtkTableCell implementation so
+  // that tests can still be run.
+  auto cell_properties = std::make_unique<base::ListValue>();
+  if (role == ATK_ROLE_TABLE_CELL || role == ATK_ROLE_COLUMN_HEADER ||
+      role == ATK_ROLE_ROW_HEADER) {
+    int row, col, row_span, col_span;
+    int n_row_headers = 0, n_column_headers = 0;
+    auto cell_interface = ui::AtkTableCellInterface::Get();
+    if (cell_interface.has_value()) {
+      AtkTableCell* cell = G_TYPE_CHECK_INSTANCE_CAST(
+          (atk_object), cell_interface->GetType(), AtkTableCell);
+      GPtrArray* column_headers = cell_interface->GetColumnHeaderCells(cell);
+      GPtrArray* row_headers = cell_interface->GetRowHeaderCells(cell);
+      n_column_headers = column_headers->len;
+      n_row_headers = row_headers->len;
+      g_ptr_array_unref(column_headers);
+      g_ptr_array_unref(row_headers);
+      cell_interface->GetRowColumnSpan(cell, &row, &col, &row_span, &col_span);
+    } else {
+      row = ax_platform_node->GetTableRow();
+      col = ax_platform_node->GetTableColumn();
+      row_span = ax_platform_node->GetTableRowSpan();
+      col_span = ax_platform_node->GetTableColumnSpan();
+      if (role == ATK_ROLE_TABLE_CELL) {
+        auto* delegate = ax_platform_node->GetTable()->GetDelegate();
+        n_column_headers = delegate->GetColHeaderNodeIds(col).size();
+        n_row_headers = delegate->GetRowHeaderNodeIds(row).size();
+      }
+    }
+    cell_properties->AppendString(
+        base::StringPrintf("(row=%i, col=%i, row_span=%i, col_span=%i", row,
+                           col, row_span, col_span));
+    cell_properties->AppendString(
+        base::StringPrintf("n_row_headers=%i, n_col_headers=%i)", n_row_headers,
+                           n_column_headers));
+  }
+  dict->Set("cell", std::move(cell_properties));
 }
 
 void AccessibilityTreeFormatterAuraLinux::AddProperties(
diff --git a/content/browser/child_process_security_policy_impl.cc b/content/browser/child_process_security_policy_impl.cc
index 3901695..84115f3 100644
--- a/content/browser/child_process_security_policy_impl.cc
+++ b/content/browser/child_process_security_policy_impl.cc
@@ -434,11 +434,13 @@
     const url::Origin& origin,
     BrowsingInstanceId min_browsing_instance_id,
     BrowserContext* browser_context,
-    ResourceContext* resource_context)
+    ResourceContext* resource_context,
+    bool isolate_all_subdomains)
     : origin_(origin),
       min_browsing_instance_id_(min_browsing_instance_id),
       browser_context_(browser_context),
-      resource_context_(resource_context) {
+      resource_context_(resource_context),
+      isolate_all_subdomains_(isolate_all_subdomains) {
   // If there is a BrowserContext, there must also be a ResourceContext
   // associated with this entry.
   DCHECK_EQ(!browser_context, !resource_context);
@@ -1388,37 +1390,36 @@
 }
 
 void ChildProcessSecurityPolicyImpl::AddIsolatedOrigins(
-    std::vector<url::Origin> origins_to_add,
+    const std::vector<url::Origin>& origins_to_add,
+    BrowserContext* browser_context) {
+  std::vector<IsolatedOriginPattern> patterns;
+  patterns.reserve(origins_to_add.size());
+  std::transform(origins_to_add.cbegin(), origins_to_add.cend(),
+                 std::back_inserter(patterns),
+                 [](const url::Origin& o) -> IsolatedOriginPattern {
+                   return IsolatedOriginPattern(o);
+                 });
+  AddIsolatedOrigins(patterns, browser_context);
+}
+
+void ChildProcessSecurityPolicyImpl::AddIsolatedOrigins(
+    const std::vector<IsolatedOriginPattern>& patterns,
     BrowserContext* browser_context) {
   // This can only be called from the UI thread, as it reads state that's only
   // available (and is only safe to be retrieved) on the UI thread, such as
   // BrowsingInstance IDs.
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-  // Filter out origins that cannot be used as an isolated origin.
-  base::EraseIf(origins_to_add, [](const url::Origin& origin) {
-    if (IsolatedOriginUtil::IsValidIsolatedOrigin(origin))
-      return false;  // Don't remove.
-
-    LOG(ERROR) << "Invalid isolated origin: " << origin;
-    return true;  // Remove.
-  });
-
-  // Ports are ignored when matching isolated origins (see also
-  // https://crbug.com/914511).
-  for (url::Origin& origin : origins_to_add) {
-    const std::string& scheme = origin.scheme();
-    int default_port =
-        url::DefaultPortForScheme(scheme.data(), scheme.length());
-    if (origin.port() != default_port) {
-      LOG(ERROR) << "Ignoring port number in isolated origin: " << origin;
-      origin = url::Origin::Create(GURL(
-          origin.scheme() + url::kStandardSchemeSeparator + origin.host()));
-    }
-  }
-
   base::AutoLock isolated_origins_lock(isolated_origins_lock_);
-  for (url::Origin& origin : origins_to_add) {
+
+  for (const IsolatedOriginPattern& pattern : patterns) {
+    if (!pattern.is_valid()) {
+      LOG(ERROR) << "Invalid isolated origin: " << pattern.pattern();
+      continue;
+    }
+
+    url::Origin origin_to_add = pattern.origin();
+
     // GetSiteForOrigin() is used to look up the site URL of |origin| to speed
     // up the isolated origin lookup.  This only performs a straightforward
     // translation of an origin to eTLD+1; it does *not* take into account
@@ -1426,7 +1427,7 @@
     // here, but *is* typically needed for making process model decisions. Be
     // very careful about using GetSiteForOrigin() elsewhere, and consider
     // whether you should be using GetSiteForURL() instead.
-    GURL key(SiteInstanceImpl::GetSiteForOrigin(origin));
+    GURL key(SiteInstanceImpl::GetSiteForOrigin(origin_to_add));
 
     // Isolated origins should apply only to future BrowsingInstances and
     // processes.  Save the first BrowsingInstance ID to which they should
@@ -1438,7 +1439,7 @@
     // need to be added again.
     bool should_add = true;
     for (const auto& entry : isolated_origins_[key]) {
-      if (entry.origin() != origin)
+      if (entry.origin() != origin_to_add)
         continue;
 
       // If the added origin already exists for the same BrowserContext, don't
@@ -1463,9 +1464,10 @@
     if (should_add) {
       ResourceContext* resource_context =
           browser_context ? browser_context->GetResourceContext() : nullptr;
-      IsolatedOriginEntry entry(std::move(origin), min_browsing_instance_id,
-                                browser_context, resource_context);
-      isolated_origins_[key].insert(std::move(entry));
+      IsolatedOriginEntry entry(
+          std::move(origin_to_add), min_browsing_instance_id, browser_context,
+          resource_context, pattern.isolate_all_subdomains());
+      isolated_origins_[key].emplace_back(std::move(entry));
     }
   }
 }
@@ -1557,7 +1559,7 @@
   }
 
   // Looks for all isolated origins that were already isolated at the time
-  // |isolation_context| was created.  If multiple isolated origins are
+  // |isolation_context| was created. If multiple isolated origins are
   // registered with a common domain suffix, return the most specific one.  For
   // example, if foo.isolated.com and isolated.com are both isolated origins,
   // bar.foo.isolated.com should return foo.isolated.com.
@@ -1576,6 +1578,16 @@
       if (matches_browsing_instance_id &&
           IsolatedOriginUtil::DoesOriginMatchIsolatedOrigin(
               origin, isolated_origin_entry.origin())) {
+        // If a match has been found that requires all subdomains to be
+        // isolated then return immediately. |origin| is returned to ensure
+        // proper process isolation, e.g. https://a.b.c.isolated.com matches
+        // an IsolatedOriginEntry constructed from http://**.isolated.com, so
+        // https://a.b.c.isolated.com must be returned.
+        if (isolated_origin_entry.isolate_all_subdomains()) {
+          *result = origin;
+          return true;
+        }
+
         if (!found || result->host().length() <
                           isolated_origin_entry.origin().host().length()) {
           *result = isolated_origin_entry.origin();
diff --git a/content/browser/child_process_security_policy_impl.h b/content/browser/child_process_security_policy_impl.h
index 31f391c..552d3fa 100644
--- a/content/browser/child_process_security_policy_impl.h
+++ b/content/browser/child_process_security_policy_impl.h
@@ -20,6 +20,7 @@
 #include "base/memory/singleton.h"
 #include "base/synchronization/lock.h"
 #include "base/thread_annotations.h"
+#include "content/browser/isolated_origin_util.h"
 #include "content/browser/isolation_context.h"
 #include "content/public/browser/child_process_security_policy.h"
 #include "content/public/common/resource_type.h"
@@ -39,7 +40,7 @@
 namespace storage {
 class FileSystemContext;
 class FileSystemURL;
-}
+}  // namespace storage
 
 namespace content {
 
@@ -100,7 +101,9 @@
   bool HasWebUIBindings(int child_id) override;
   void GrantSendMidiSysExMessage(int child_id) override;
   bool CanAccessDataForOrigin(int child_id, const GURL& url) override;
-  void AddIsolatedOrigins(std::vector<url::Origin> origins,
+  void AddIsolatedOrigins(const std::vector<url::Origin>& origins,
+                          BrowserContext* browser_context = nullptr) override;
+  void AddIsolatedOrigins(const std::vector<IsolatedOriginPattern>& patterns,
                           BrowserContext* browser_context = nullptr) override;
   bool IsGloballyIsolatedOriginForTesting(const url::Origin& origin) override;
 
@@ -335,6 +338,12 @@
                            IsolatedOriginsForSpecificBrowserContexts);
   FRIEND_TEST_ALL_PREFIXES(ChildProcessSecurityPolicyTest,
                            IsolatedOriginsRemovedWhenBrowserContextDestroyed);
+  FRIEND_TEST_ALL_PREFIXES(ChildProcessSecurityPolicyTest,
+                           IsolateAllSuborigins);
+  FRIEND_TEST_ALL_PREFIXES(ChildProcessSecurityPolicyTest,
+                           WildcardAndNonWildcardOrigins);
+  FRIEND_TEST_ALL_PREFIXES(ChildProcessSecurityPolicyTest,
+                           WildcardAndNonWildcardEmbedded);
 
   class SecurityState;
 
@@ -350,7 +359,9 @@
     IsolatedOriginEntry(const url::Origin& origin,
                         BrowsingInstanceId min_browsing_instance_id,
                         BrowserContext* browser_context,
-                        ResourceContext* resource_context);
+                        ResourceContext* resource_context,
+                        bool isolate_all_subdomains);
+
     // Copyable and movable.
     IsolatedOriginEntry(const IsolatedOriginEntry& other);
     IsolatedOriginEntry& operator=(const IsolatedOriginEntry& other);
@@ -361,16 +372,18 @@
     // Allow this class to be used as a key in STL.
     bool operator<(const IsolatedOriginEntry& other) const {
       return std::tie(origin_, min_browsing_instance_id_, browser_context_,
-                      resource_context_) <
+                      resource_context_, isolate_all_subdomains_) <
              std::tie(other.origin_, other.min_browsing_instance_id_,
-                      other.browser_context_, other.resource_context_);
+                      other.browser_context_, other.resource_context_,
+                      other.isolate_all_subdomains_);
     }
 
     bool operator==(const IsolatedOriginEntry& other) const {
       return origin_ == other.origin_ &&
              min_browsing_instance_id_ == other.min_browsing_instance_id_ &&
              browser_context_ == other.browser_context_ &&
-             resource_context_ == other.resource_context_;
+             resource_context_ == other.resource_context_ &&
+             isolate_all_subdomains_ == other.isolate_all_subdomains_;
     }
 
     // True if this isolated origin applies globally to all profiles.
@@ -390,6 +403,8 @@
 
     const BrowserContext* browser_context() const { return browser_context_; }
 
+    bool isolate_all_subdomains() const { return isolate_all_subdomains_; }
+
    private:
     url::Origin origin_;
     BrowsingInstanceId min_browsing_instance_id_;
@@ -401,6 +416,14 @@
     BrowserContext* browser_context_;
     ResourceContext* resource_context_;
 
+    // True if origins at this or lower level should be treated as distinct
+    // isolated origins, effectively isolating all domains below a given domain,
+    // e.g. if the origin is https://foo.com and isolate_all_subdomains_ is
+    // true, then https://bar.foo.com, https://qux.bar.foo.com and all
+    // subdomains of the form https://<<any pattern here>>.foo.com are
+    // considered isolated origins.
+    bool isolate_all_subdomains_;
+
     // TODO(alexmos): Track the source of each isolated origin entry, e.g., to
     // distinguish those that should be displayed to the user from those that
     // should not.  See https://crbug.com/920911.
@@ -426,10 +449,9 @@
   // Grants access permission to the given isolated file system
   // identified by |filesystem_id|.  See comments for
   // ChildProcessSecurityPolicy::GrantReadFileSystem() for more details.
-  void GrantPermissionsForFileSystem(
-      int child_id,
-      const std::string& filesystem_id,
-      int permission);
+  void GrantPermissionsForFileSystem(int child_id,
+                                     const std::string& filesystem_id,
+                                     int permission);
 
   // Determines if certain permissions were granted for a file. |permissions|
   // is an internally defined bit-set.
@@ -446,10 +468,9 @@
 
   // Determines if certain permissions were granted for a file system.
   // |permissions| is an internally defined bit-set.
-  bool HasPermissionsForFileSystem(
-      int child_id,
-      const std::string& filesystem_id,
-      int permission);
+  bool HasPermissionsForFileSystem(int child_id,
+                                   const std::string& filesystem_id,
+                                   int permission);
 
   // Gets the SecurityState object associated with |child_id|.
   // Note: Returned object is only valid for the duration the caller holds
@@ -529,7 +550,7 @@
   //      represents https://test.foo.com being isolated in profile1 starting
   //      with BrowsingInstance ID 4, and also in profile2 starting with
   //      BrowsingInstance ID 7.
-  base::flat_map<GURL, base::flat_set<IsolatedOriginEntry>> isolated_origins_
+  base::flat_map<GURL, std::vector<IsolatedOriginEntry>> isolated_origins_
       GUARDED_BY(isolated_origins_lock_);
 
   DISALLOW_COPY_AND_ASSIGN(ChildProcessSecurityPolicyImpl);
diff --git a/content/browser/child_process_security_policy_unittest.cc b/content/browser/child_process_security_policy_unittest.cc
index d62db81..92a30f6 100644
--- a/content/browser/child_process_security_policy_unittest.cc
+++ b/content/browser/child_process_security_policy_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/test/bind_test_util.h"
 #include "base/test/mock_log.h"
 #include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/isolated_origin_util.h"
 #include "content/browser/site_instance_impl.h"
 #include "content/public/common/bindings_policy.h"
 #include "content/public/common/url_constants.h"
@@ -99,20 +100,23 @@
   //     where site_url is created from |origin| and
   //           entry contains |origin| and |min_browsing_instance_id|.
   auto GetIsolatedOriginEntry(int min_browsing_instance_id,
-                              const url::Origin& origin) {
-    return std::pair<GURL, base::flat_set<IsolatedOriginEntry>>(
+                              const url::Origin& origin,
+                              bool isolate_all_subdomains = false) {
+    return std::pair<GURL, std::vector<IsolatedOriginEntry>>(
         SiteInstanceImpl::GetSiteForOrigin(origin),
         {IsolatedOriginEntry(
             origin,
             BrowsingInstanceId::FromUnsafeValue(min_browsing_instance_id),
-            nullptr, nullptr)});
+            nullptr, nullptr, isolate_all_subdomains)});
   }
   // Converts |origin| -> (site_url, {entry})
   //     where site_url is created from |origin| and
   //           entry contains |origin| and the latest BrowsingInstance ID.
-  auto GetIsolatedOriginEntry(const url::Origin& origin) {
+  auto GetIsolatedOriginEntry(const url::Origin& origin,
+                              bool isolate_all_subdomains = false) {
     return GetIsolatedOriginEntry(
-        SiteInstanceImpl::NextBrowsingInstanceId().GetUnsafeValue(), origin);
+        SiteInstanceImpl::NextBrowsingInstanceId().GetUnsafeValue(), origin,
+        isolate_all_subdomains);
   }
   // Converts |origin1|, |origin2| -> (site_url, {entry1, entry2})
   //     where |site_url| is created from |origin1|, but is assumed to be the
@@ -121,17 +125,19 @@
   //           entry1 contains |origin1| and the latest BrowsingInstance ID,
   //           entry2 contains |origin2| and the latest BrowsingInstance ID.
   auto GetIsolatedOriginEntry(const url::Origin& origin1,
-                              const url::Origin& origin2) {
+                              const url::Origin& origin2,
+                              bool origin1_isolate_all_subdomains = false,
+                              bool origin2_isolate_all_subdomains = false) {
     EXPECT_EQ(SiteInstanceImpl::GetSiteForOrigin(origin1),
               SiteInstanceImpl::GetSiteForOrigin(origin2));
-    return std::pair<GURL, base::flat_set<IsolatedOriginEntry>>(
+    return std::pair<GURL, std::vector<IsolatedOriginEntry>>(
         SiteInstanceImpl::GetSiteForOrigin(origin1),
         {IsolatedOriginEntry(origin1,
                              SiteInstanceImpl::NextBrowsingInstanceId(),
-                             nullptr, nullptr),
-         IsolatedOriginEntry(origin2,
-                             SiteInstanceImpl::NextBrowsingInstanceId(),
-                             nullptr, nullptr)});
+                             nullptr, nullptr, origin1_isolate_all_subdomains),
+         IsolatedOriginEntry(
+             origin2, SiteInstanceImpl::NextBrowsingInstanceId(), nullptr,
+             nullptr, origin2_isolate_all_subdomains)});
   }
 
   bool IsIsolatedOrigin(BrowserContext* context,
@@ -160,6 +166,15 @@
                          });
   }
 
+  void CheckGetSiteForURL(BrowserContext* context,
+                          std::map<GURL, GURL> to_test) {
+    for (const auto& entry : to_test) {
+      EXPECT_EQ(SiteInstanceImpl::GetSiteForURL(IsolationContext(context),
+                                                entry.first),
+                entry.second);
+    }
+  }
+
  protected:
   void RegisterTestScheme(const std::string& scheme) {
     test_browser_client_.AddScheme(scheme);
@@ -1396,6 +1411,274 @@
   p->RemoveIsolatedOriginForTesting(bar);
   p->RemoveIsolatedOriginForTesting(baz);
   p->RemoveIsolatedOriginForTesting(baz_http);
+
+  // We should have removed all isolated origins at this point.
+  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
+                     testing::IsEmpty());
+}
+
+TEST_F(ChildProcessSecurityPolicyTest, IsolateAllSuborigins) {
+  url::Origin qux = url::Origin::Create(GURL("https://qux.com/"));
+  IsolatedOriginPattern etld1_wild("https://**.foo.com");
+  IsolatedOriginPattern etld2_wild("https://**.bar.foo.com");
+  url::Origin etld1 = url::Origin::Create(GURL("https://foo.com"));
+  url::Origin etld2 = url::Origin::Create(GURL("https://bar.foo.com"));
+
+  ChildProcessSecurityPolicyImpl* p =
+      ChildProcessSecurityPolicyImpl::GetInstance();
+
+  // Check we can add a single wildcard origin.
+  p->AddIsolatedOrigins({etld1_wild});
+
+  LOCKED_EXPECT_THAT(
+      p->isolated_origins_lock_, p->isolated_origins_,
+      testing::UnorderedElementsAre(GetIsolatedOriginEntry(etld1, true)));
+
+  // Add a conventional origin and check they can live side by side.
+  p->AddIsolatedOrigins({qux});
+  LOCKED_EXPECT_THAT(
+      p->isolated_origins_lock_, p->isolated_origins_,
+      testing::UnorderedElementsAre(GetIsolatedOriginEntry(etld1, true),
+                                    GetIsolatedOriginEntry(qux, false)));
+
+  // Check that a wildcard domain within another wildcard domain can be added.
+  p->AddIsolatedOrigins({etld2_wild});
+  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
+                     testing::UnorderedElementsAre(
+                         GetIsolatedOriginEntry(etld1, etld2, true, true),
+                         GetIsolatedOriginEntry(qux, false)));
+
+  // Check that removing a single wildcard domain, that contains another
+  // wildcard domain, doesn't affect the isolating behavior of the original
+  // wildcard domain.
+  p->RemoveIsolatedOriginForTesting(etld1);
+  LOCKED_EXPECT_THAT(
+      p->isolated_origins_lock_, p->isolated_origins_,
+      testing::UnorderedElementsAre(GetIsolatedOriginEntry(etld2, true),
+                                    GetIsolatedOriginEntry(qux, false)));
+
+  // Removing remaining domains.
+  p->RemoveIsolatedOriginForTesting(qux);
+  p->RemoveIsolatedOriginForTesting(etld2);
+
+  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
+                     testing::IsEmpty());
+}
+
+// Verify that the isolation behavior for wildcard and non-wildcard origins,
+// singly or in concert, behaves correctly via calls to GetSiteForURL().
+TEST_F(ChildProcessSecurityPolicyTest, WildcardAndNonWildcardOrigins) {
+  ChildProcessSecurityPolicyImpl* p =
+      ChildProcessSecurityPolicyImpl::GetInstance();
+
+  // There should be no isolated origins before this test starts.
+  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
+                     testing::IsEmpty());
+
+  // Construct a simple case, a single isolated origin.
+  //  IsolatedOriginPattern isolated("https://isolated.com");
+  IsolatedOriginPattern inner_isolated("https://inner.isolated.com");
+  IsolatedOriginPattern wildcard("https://**.wildcard.com");
+  IsolatedOriginPattern inner_wildcard("https://**.inner.wildcard.com");
+
+  GURL isolated_url("https://isolated.com");
+  GURL inner_isolated_url("https://inner.isolated.com");
+  GURL host_inner_isolated_url("https://host.inner.isolated.com");
+  GURL wildcard_url("https://wildcard.com");
+  GURL inner_wildcard_url("https://inner.wildcard.com");
+  GURL host_inner_wildcard_url("https://host.inner.wildcard.com");
+  GURL unrelated_url("https://unrelated.com");
+
+  // Verify the isolation behavior of the test patterns before isolating any
+  // domains.
+  std::map<GURL, GURL> origins_site_test_map{
+      {isolated_url, isolated_url},
+      {inner_isolated_url, isolated_url},
+      {host_inner_isolated_url, isolated_url},
+      {wildcard_url, wildcard_url},
+      {inner_wildcard_url, wildcard_url},
+      {host_inner_wildcard_url, wildcard_url},
+      {unrelated_url, unrelated_url},
+  };
+  CheckGetSiteForURL(browser_context(), origins_site_test_map);
+
+  // Add |wildcard|, a wildcard origin from a different domain, then verify that
+  // the existing behavior of |isolated_url| and |inner_isolated_url| remains
+  // unaffected, while all subdomains of wildcard.com are returned as unique
+  // sites.
+  p->AddIsolatedOrigins({wildcard});
+  origins_site_test_map[inner_wildcard_url] = inner_wildcard_url;
+  origins_site_test_map[host_inner_wildcard_url] = host_inner_wildcard_url;
+  CheckGetSiteForURL(browser_context(), origins_site_test_map);
+
+  // Add |inner_isolated|, then verify that querying for |inner_isolated_url|
+  // returns |inner_isolated_url| while leaving the wildcard origins unaffected.
+  p->AddIsolatedOrigins({inner_isolated});
+  origins_site_test_map[inner_isolated_url] = inner_isolated_url;
+  origins_site_test_map[host_inner_isolated_url] = inner_isolated_url;
+  CheckGetSiteForURL(browser_context(), origins_site_test_map);
+
+  // Add |inner_wildcard|. This should not change the behavior of the test
+  // above as all subdomains of |inner_wildcard| are contained within
+  // |wildcard|.
+  p->AddIsolatedOrigins({inner_wildcard});
+  CheckGetSiteForURL(browser_context(), origins_site_test_map);
+
+  p->RemoveIsolatedOriginForTesting(wildcard.origin());
+  p->RemoveIsolatedOriginForTesting(inner_isolated.origin());
+  p->RemoveIsolatedOriginForTesting(inner_wildcard.origin());
+
+  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
+                     testing::IsEmpty());
+}
+
+TEST_F(ChildProcessSecurityPolicyTest, WildcardAndNonWildcardEmbedded) {
+  ChildProcessSecurityPolicyImpl* p =
+      ChildProcessSecurityPolicyImpl::GetInstance();
+
+  // There should be no isolated origins before this test starts.
+  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
+                     testing::IsEmpty());
+
+  {
+    // Test the behavior of a wildcard origin contained within a single
+    // isolated origin. Removing the isolated origin should have no effect on
+    // the wildcard origin.
+    IsolatedOriginPattern isolated("https://isolated.com");
+    IsolatedOriginPattern wildcard_isolated("https://**.wildcard.isolated.com");
+
+    GURL isolated_url("https://isolated.com");
+    GURL a_isolated_url("https://a.isolated.com");
+    GURL wildcard_isolated_url("https://wildcard.isolated.com");
+    GURL a_wildcard_isolated_url("https://a.wildcard.isolated.com");
+
+    p->AddIsolatedOrigins({isolated, wildcard_isolated});
+    std::map<GURL, GURL> origin_site_map{
+        {isolated_url, isolated_url},
+        {a_isolated_url, isolated_url},
+        {wildcard_isolated_url, wildcard_isolated_url},
+        {a_wildcard_isolated_url, a_wildcard_isolated_url},
+    };
+
+    CheckGetSiteForURL(browser_context(), origin_site_map);
+
+    p->RemoveIsolatedOriginForTesting(isolated.origin());
+    p->RemoveIsolatedOriginForTesting(wildcard_isolated.origin());
+  }
+
+  // No isolated origins should persist between tests.
+  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
+                     testing::IsEmpty());
+
+  {
+    // A single isolated origin is nested within a wildcard origin. In this
+    // scenario the wildcard origin supersedes isolated origins.
+    IsolatedOriginPattern wildcard("https://**.wildcard.com");
+    IsolatedOriginPattern isolated_wildcard("https://isolated.wildcard.com");
+
+    GURL wildcard_url("https://wildcard.com");
+    GURL a_wildcard_url("https://a.wildcard.com");
+    GURL isolated_wildcard_url("https://isolated.wildcard.com");
+    GURL a_isolated_wildcard_url("https://a.isolated.wildcard.com");
+
+    p->AddIsolatedOrigins({wildcard, isolated_wildcard});
+    std::map<GURL, GURL> origin_site_map{
+        {wildcard_url, wildcard_url},
+        {a_wildcard_url, a_wildcard_url},
+        {isolated_wildcard_url, isolated_wildcard_url},
+        {a_isolated_wildcard_url, a_isolated_wildcard_url},
+    };
+
+    CheckGetSiteForURL(browser_context(), origin_site_map);
+
+    p->RemoveIsolatedOriginForTesting(wildcard.origin());
+    p->RemoveIsolatedOriginForTesting(isolated_wildcard.origin());
+  }
+
+  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
+                     testing::IsEmpty());
+
+  {
+    // Nest wildcard isolated origins within each other. Verify that removing
+    // the outer wildcard origin doesn't affect the inner one.
+    IsolatedOriginPattern outer("https://**.outer.com");
+    IsolatedOriginPattern inner("https://**.inner.outer.com");
+
+    GURL outer_url("https://outer.com");
+    GURL a_outer_url("https://a.outer.com");
+    GURL inner_url("https://inner.outer.com");
+    GURL a_inner_url("https://a.inner.outer.com");
+
+    p->AddIsolatedOrigins({inner, outer});
+
+    std::map<GURL, GURL> origin_site_map{
+        {outer_url, outer_url},
+        {a_outer_url, a_outer_url},
+        {inner_url, inner_url},
+        {a_inner_url, a_inner_url},
+    };
+
+    CheckGetSiteForURL(browser_context(), origin_site_map);
+    p->RemoveIsolatedOriginForTesting(outer.origin());
+    p->RemoveIsolatedOriginForTesting(inner.origin());
+  }
+
+  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
+                     testing::IsEmpty());
+
+  // Verify that adding a wildcard domain then a then a conventional domain
+  // doesn't affect the isolating behavior of the wildcard, i.e. whichever
+  // isolated domain is added entered 'wins'.
+  {
+    IsolatedOriginPattern wild("https://**.bar.foo.com");
+    IsolatedOriginPattern single("https://bar.foo.com");
+
+    GURL host_url("https://host.bar.foo.com");
+
+    p->AddIsolatedOrigins({wild});
+    std::map<GURL, GURL> origin_site_map{
+        {host_url, host_url},
+    };
+
+    CheckGetSiteForURL(browser_context(), origin_site_map);
+
+    p->AddIsolatedOrigins({single});
+
+    CheckGetSiteForURL(browser_context(), origin_site_map);
+
+    p->RemoveIsolatedOriginForTesting(wild.origin());
+    p->RemoveIsolatedOriginForTesting(single.origin());
+  }
+
+  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
+                     testing::IsEmpty());
+
+  // Verify the first domain added remains dominant in the case of differing
+  // wildcard and non-wildcard statuses.
+  {
+    IsolatedOriginPattern wild("https://**.bar.foo.com");
+    IsolatedOriginPattern single("https://bar.foo.com");
+
+    GURL host_url("https://host.bar.foo.com");
+    GURL domain_url("https://bar.foo.com");
+
+    p->AddIsolatedOrigins({single});
+    std::map<GURL, GURL> origin_site_map{
+        {host_url, domain_url},
+    };
+
+    CheckGetSiteForURL(browser_context(), origin_site_map);
+
+    p->AddIsolatedOrigins({wild});
+
+    CheckGetSiteForURL(browser_context(), origin_site_map);
+
+    p->RemoveIsolatedOriginForTesting(wild.origin());
+    p->RemoveIsolatedOriginForTesting(single.origin());
+  }
+
+  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
+                     testing::IsEmpty());
 }
 
 // Verifies that isolated origins only apply to future BrowsingInstances.
@@ -1811,4 +2094,81 @@
   EXPECT_FALSE(io_after_remove_complete);
 }
 
+TEST_F(ChildProcessSecurityPolicyTest, IsolatedOriginPattern) {
+  const base::StringPiece etld1_wild("https://**.foo.com");
+  url::Origin etld1_wild_origin = url::Origin::Create(GURL("https://foo.com"));
+  IsolatedOriginPattern p(etld1_wild);
+  EXPECT_TRUE(p.isolate_all_subdomains());
+  EXPECT_TRUE(p.is_valid());
+  EXPECT_EQ(p.origin(), etld1_wild_origin);
+
+  const base::StringPiece etld2_wild("https://**.bar.foo.com");
+  url::Origin etld2_wild_origin =
+      url::Origin::Create(GURL("https://bar.foo.com"));
+  bool result = p.Parse(etld2_wild);
+  EXPECT_TRUE(result);
+  EXPECT_TRUE(p.isolate_all_subdomains());
+  EXPECT_TRUE(p.is_valid());
+  EXPECT_EQ(p.origin(), etld2_wild_origin);
+  EXPECT_FALSE(p.origin().opaque());
+
+  const base::StringPiece etld1("https://baz.com");
+  url::Origin etld1_origin = url::Origin::Create(GURL("https://baz.com"));
+  result = p.Parse(etld1);
+  EXPECT_TRUE(result);
+  EXPECT_FALSE(p.isolate_all_subdomains());
+  EXPECT_TRUE(p.is_valid());
+  EXPECT_EQ(p.origin(), etld1_origin);
+  EXPECT_FALSE(p.origin().opaque());
+
+  const base::StringPiece bad_scheme("ftp://foo.com");
+  result = p.Parse(bad_scheme);
+  EXPECT_FALSE(result);
+  EXPECT_FALSE(p.isolate_all_subdomains());
+  EXPECT_FALSE(p.is_valid());
+  EXPECT_TRUE(p.origin().opaque());
+
+  const base::StringPiece no_scheme_sep("httpsfoo.com");
+  result = p.Parse(no_scheme_sep);
+  EXPECT_FALSE(result);
+  EXPECT_FALSE(p.isolate_all_subdomains());
+  EXPECT_FALSE(p.is_valid());
+  EXPECT_TRUE(p.origin().opaque());
+
+  const base::StringPiece bad_registry("https://co.uk");
+  result = p.Parse(bad_registry);
+  EXPECT_FALSE(result);
+  EXPECT_FALSE(p.isolate_all_subdomains());
+  EXPECT_FALSE(p.is_valid());
+  EXPECT_TRUE(p.origin().opaque());
+
+  const base::StringPiece trailing_dot("https://bar.com.");
+  result = p.Parse(trailing_dot);
+  EXPECT_FALSE(result);
+  EXPECT_FALSE(p.isolate_all_subdomains());
+  EXPECT_FALSE(p.is_valid());
+  EXPECT_TRUE(p.origin().opaque());
+
+  const base::StringPiece ip_addr("https://10.20.30.40");
+  url::Origin ip_origin = url::Origin::Create(GURL("https://10.20.30.40"));
+  result = p.Parse(ip_addr);
+  EXPECT_TRUE(result);
+  EXPECT_FALSE(p.isolate_all_subdomains());
+  EXPECT_FALSE(p.origin().opaque());
+  EXPECT_TRUE(p.is_valid());
+  EXPECT_EQ(p.origin(), ip_origin);
+
+  const base::StringPiece wild_ip_addr("https://**.10.20.30.40");
+  result = p.Parse(wild_ip_addr);
+  EXPECT_FALSE(result);
+  EXPECT_FALSE(p.isolate_all_subdomains());
+  EXPECT_FALSE(p.is_valid());
+
+  const url::Origin bad_origin;
+  IsolatedOriginPattern bad_pattern(bad_origin);
+  EXPECT_FALSE(bad_pattern.isolate_all_subdomains());
+  EXPECT_TRUE(bad_pattern.origin().opaque());
+  EXPECT_FALSE(p.is_valid());
+}
+
 }  // namespace content
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index d30acb17..5426a98c 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -6340,33 +6340,6 @@
                                             base::debug::CrashKeySize::Size256),
         GetSiteInstance()->lock_url().spec());
 
-    base::debug::SetCrashKeyString(
-        base::debug::AllocateCrashKeyString("original_url_origin",
-                                            base::debug::CrashKeySize::Size256),
-        GetSiteInstance()->original_url().GetOrigin().spec());
-
-    base::debug::SetCrashKeyString(
-        base::debug::AllocateCrashKeyString("is_transfer_needed",
-                                            base::debug::CrashKeySize::Size32),
-        bool_to_crash_key(frame_tree_node_->render_manager()
-                              ->IsRendererTransferNeededForNavigation(
-                                  this, validated_params->url)));
-
-    base::debug::SetCrashKeyString(
-        base::debug::AllocateCrashKeyString("is_mhtml_document",
-                                            base::debug::CrashKeySize::Size32),
-        bool_to_crash_key(is_mhtml_document()));
-
-    base::debug::SetCrashKeyString(
-        base::debug::AllocateCrashKeyString("last_committed_url_origin",
-                                            base::debug::CrashKeySize::Size256),
-        GetLastCommittedURL().GetOrigin().spec());
-
-    base::debug::SetCrashKeyString(
-        base::debug::AllocateCrashKeyString("last_successful_url_origin",
-                                            base::debug::CrashKeySize::Size256),
-        last_successful_url().GetOrigin().spec());
-
     if (navigation_request && navigation_request->navigation_handle()) {
       NavigationHandleImpl* handle = navigation_request->navigation_handle();
       base::debug::SetCrashKeyString(
@@ -6391,11 +6364,6 @@
 
       base::debug::SetCrashKeyString(
           base::debug::AllocateCrashKeyString(
-              "from_begin_navigation", base::debug::CrashKeySize::Size32),
-          bool_to_crash_key(navigation_request->from_begin_navigation()));
-
-      base::debug::SetCrashKeyString(
-          base::debug::AllocateCrashKeyString(
               "net_error_code", base::debug::CrashKeySize::Size32),
           base::NumberToString(navigation_request->net_error()));
 
@@ -6410,17 +6378,6 @@
           base::debug::AllocateCrashKeyString(
               "starting_site_instance", base::debug::CrashKeySize::Size64),
           handle->GetStartingSiteInstance()->GetSiteURL().spec());
-
-      // Recompute the target SiteInstance to see if it matches the current one
-      // at commit time.
-      scoped_refptr<SiteInstance> dest_instance =
-          frame_tree_node_->render_manager()
-              ->GetSiteInstanceForNavigationRequest(*navigation_request);
-      base::debug::SetCrashKeyString(
-          base::debug::AllocateCrashKeyString(
-              "does_recomputed_site_instance_match_current",
-              base::debug::CrashKeySize::Size32),
-          bool_to_crash_key(dest_instance == GetSiteInstance()));
     }
 
     // Kills the process.
diff --git a/content/browser/isolated_origin_util.cc b/content/browser/isolated_origin_util.cc
index 5c36547..7c2bfd5 100644
--- a/content/browser/isolated_origin_util.cc
+++ b/content/browser/isolated_origin_util.cc
@@ -2,14 +2,89 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <string>
+
 #include "content/browser/isolated_origin_util.h"
 
 #include "base/strings/string_util.h"
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
 #include "url/gurl.h"
 
+const char* kAllSubdomainsWildcard = "**.";
+
 namespace content {
 
+IsolatedOriginPattern::IsolatedOriginPattern(base::StringPiece pattern)
+    : isolate_all_subdomains_(false), is_valid_(false) {
+  Parse(pattern);
+}
+
+IsolatedOriginPattern::IsolatedOriginPattern(const url::Origin& origin)
+    : IsolatedOriginPattern(origin.GetURL().spec()) {}
+
+IsolatedOriginPattern::~IsolatedOriginPattern() = default;
+IsolatedOriginPattern::IsolatedOriginPattern(
+    const IsolatedOriginPattern& other) = default;
+IsolatedOriginPattern& IsolatedOriginPattern::operator=(
+    const IsolatedOriginPattern& other) = default;
+IsolatedOriginPattern::IsolatedOriginPattern(IsolatedOriginPattern&& other) =
+    default;
+IsolatedOriginPattern& IsolatedOriginPattern::operator=(
+    IsolatedOriginPattern&& other) = default;
+
+bool IsolatedOriginPattern::Parse(const base::StringPiece& unparsed_pattern) {
+  pattern_ = unparsed_pattern.as_string();
+  origin_ = url::Origin();
+  isolate_all_subdomains_ = false;
+  is_valid_ = false;
+
+  size_t host_begin = unparsed_pattern.find(url::kStandardSchemeSeparator);
+  if (host_begin == base::StringPiece::npos || host_begin == 0)
+    return false;
+
+  // Skip over the scheme separator.
+  host_begin += strlen(url::kStandardSchemeSeparator);
+  if (host_begin >= unparsed_pattern.size())
+    return false;
+
+  base::StringPiece scheme_part = unparsed_pattern.substr(0, host_begin);
+  base::StringPiece host_part = unparsed_pattern.substr(host_begin);
+
+  // Empty schemes or hosts are invalid for isolation purposes.
+  if (host_part.size() == 0)
+    return false;
+
+  if (host_part.starts_with(kAllSubdomainsWildcard)) {
+    isolate_all_subdomains_ = true;
+    host_part.remove_prefix(strlen(kAllSubdomainsWildcard));
+  }
+
+  GURL conformant_url(base::JoinString({scheme_part, host_part}, ""));
+  origin_ = url::Origin::Create(conformant_url);
+
+  // Ports are ignored when matching isolated origins (see also
+  // https://crbug.com/914511).
+  const std::string& scheme = origin_.scheme();
+  int default_port = url::DefaultPortForScheme(scheme.data(), scheme.length());
+  if (origin_.port() != default_port) {
+    LOG(ERROR) << "Ignoring port number in isolated origin: " << origin_;
+    origin_ = url::Origin::Create(GURL(
+        origin_.scheme() + url::kStandardSchemeSeparator + origin_.host()));
+  }
+
+  // Can't isolate subdomains of an IP address, must be a valid isolated origin
+  // after processing.
+  if ((conformant_url.HostIsIPAddress() && isolate_all_subdomains_) ||
+      !IsolatedOriginUtil::IsValidIsolatedOrigin(origin_)) {
+    origin_ = url::Origin();
+    isolate_all_subdomains_ = false;
+    return false;
+  }
+
+  is_valid_ = true;
+  return true;
+}
+
 // static
 bool IsolatedOriginUtil::DoesOriginMatchIsolatedOrigin(
     const url::Origin& origin,
diff --git a/content/browser/isolated_origin_util.h b/content/browser/isolated_origin_util.h
index 4ee8037..9513d76 100644
--- a/content/browser/isolated_origin_util.h
+++ b/content/browser/isolated_origin_util.h
@@ -5,11 +5,69 @@
 #ifndef CONTENT_BROWSER_ISOLATED_ORIGIN_UTIL_H_
 #define CONTENT_BROWSER_ISOLATED_ORIGIN_UTIL_H_
 
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "base/strings/string_util.h"
 #include "content/common/content_export.h"
 #include "url/origin.h"
 
 namespace content {
 
+// This class holds isolated origin patterns, providing support for double
+// wildcard origins, e.g. https://**.foo.com indicates that all domains under
+// foo.com are to be treated as if they are distinct isolated
+// origins. Non-wildcard origins to be isolated are also supported, e.g.
+// https://bar.com.
+class CONTENT_EXPORT IsolatedOriginPattern {
+ public:
+  explicit IsolatedOriginPattern(base::StringPiece pattern);
+  explicit IsolatedOriginPattern(const url::Origin& origin);
+  ~IsolatedOriginPattern();
+
+  // Copying and moving supported.
+  IsolatedOriginPattern(const IsolatedOriginPattern& other);
+  IsolatedOriginPattern& operator=(const IsolatedOriginPattern& other);
+
+  IsolatedOriginPattern(IsolatedOriginPattern&& other);
+  IsolatedOriginPattern& operator=(IsolatedOriginPattern&& other);
+
+  bool operator==(const IsolatedOriginPattern& other) const {
+    return pattern_ == other.pattern_ && origin_ == other.origin_ &&
+           isolate_all_subdomains_ == other.isolate_all_subdomains_;
+  }
+
+  // Returns the url::Origin corresponding to the pattern supplied at
+  // construction time or via a call to Parse. In the event of parsing failure
+  // this oriqin will be opaque.
+  const url::Origin& origin() const { return origin_; }
+
+  // True if the supplied pattern was of the form https://**.foo.com, indicating
+  // all subdomains of foo.com are to be isolated.
+  bool isolate_all_subdomains() const { return isolate_all_subdomains_; }
+
+  // Return the original pattern used to construct this instance.
+  const base::StringPiece pattern() const { return pattern_; }
+
+  // Return if this origin is valid for isolation purposes.
+  bool is_valid() const { return is_valid_; }
+
+ private:
+  friend class ChildProcessSecurityPolicyTest;
+  FRIEND_TEST_ALL_PREFIXES(ChildProcessSecurityPolicyTest,
+                           IsolatedOriginPattern);
+
+  // Checks if |pattern| is a wildcard pattern, checks the scheme is one of
+  // {http, https} and constructs a url::Origin() that can be retrieved if
+  // parsing is successful. Returns true on successful parsing.
+  bool Parse(const base::StringPiece& pattern);
+
+  std::string pattern_;
+  url::Origin origin_;
+  bool isolate_all_subdomains_;
+  bool is_valid_;
+};
+
 class CONTENT_EXPORT IsolatedOriginUtil {
  public:
   // Checks whether |origin| matches the isolated origin specified by
diff --git a/content/browser/loader/prefetch_browsertest.cc b/content/browser/loader/prefetch_browsertest.cc
index 8fa10d5..2c2a541 100644
--- a/content/browser/loader/prefetch_browsertest.cc
+++ b/content/browser/loader/prefetch_browsertest.cc
@@ -68,7 +68,6 @@
 };
 
 IN_PROC_BROWSER_TEST_P(PrefetchBrowserTest, Simple) {
-  int target_fetch_count = 0;
   const char* prefetch_path = "/prefetch.html";
   const char* target_path = "/target.html";
   RegisterResponse(
@@ -80,21 +79,21 @@
       ResponseEntry("<head><title>Prefetch Target</title></head>"));
 
   base::RunLoop prefetch_waiter;
-  RegisterRequestMonitor(embedded_test_server(), target_path,
-                         &target_fetch_count, &prefetch_waiter);
+  auto request_counter = RequestCounter::CreateAndMonitor(
+      embedded_test_server(), target_path, &prefetch_waiter);
   RegisterRequestHandler(embedded_test_server());
   ASSERT_TRUE(embedded_test_server()->Start());
-  EXPECT_EQ(0, target_fetch_count);
-  EXPECT_EQ(0, prefetch_url_loader_called_);
+  EXPECT_EQ(0, request_counter->GetRequestCount());
+  EXPECT_EQ(0, GetPrefetchURLLoaderCallCount());
 
   const GURL target_url = embedded_test_server()->GetURL(target_path);
 
   // Loading a page that prefetches the target URL would increment the
-  // |target_fetch_count|.
+  // |request_counter|.
   NavigateToURL(shell(), embedded_test_server()->GetURL(prefetch_path));
   prefetch_waiter.Run();
-  EXPECT_EQ(1, target_fetch_count);
-  EXPECT_EQ(1, prefetch_url_loader_called_);
+  EXPECT_EQ(1, request_counter->GetRequestCount());
+  EXPECT_EQ(1, GetPrefetchURLLoaderCallCount());
 
   // Shutdown the server.
   EXPECT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
@@ -105,7 +104,6 @@
 }
 
 IN_PROC_BROWSER_TEST_P(PrefetchBrowserTest, CrossOrigin) {
-  int target_fetch_count = 0;
   const char* prefetch_path = "/prefetch.html";
   const char* target_path = "/target.html";
   RegisterResponse(
@@ -113,8 +111,8 @@
       ResponseEntry("<head><title>Prefetch Target</title></head>"));
 
   base::RunLoop prefetch_waiter;
-  RegisterRequestMonitor(cross_origin_server_.get(), target_path,
-                         &target_fetch_count, &prefetch_waiter);
+  auto request_counter = RequestCounter::CreateAndMonitor(
+      cross_origin_server_.get(), target_path, &prefetch_waiter);
   RegisterRequestHandler(cross_origin_server_.get());
   ASSERT_TRUE(cross_origin_server_->Start());
 
@@ -126,15 +124,15 @@
                        cross_origin_target_url.spec().c_str())));
   RegisterRequestHandler(embedded_test_server());
   ASSERT_TRUE(embedded_test_server()->Start());
-  EXPECT_EQ(0, target_fetch_count);
-  EXPECT_EQ(0, prefetch_url_loader_called_);
+  EXPECT_EQ(0, request_counter->GetRequestCount());
+  EXPECT_EQ(0, GetPrefetchURLLoaderCallCount());
 
   // Loading a page that prefetches the target URL would increment the
-  // |target_fetch_count|.
+  // |request_counter|.
   NavigateToURL(shell(), embedded_test_server()->GetURL(prefetch_path));
   prefetch_waiter.Run();
-  EXPECT_EQ(1, target_fetch_count);
-  EXPECT_EQ(1, prefetch_url_loader_called_);
+  EXPECT_EQ(1, request_counter->GetRequestCount());
+  EXPECT_EQ(1, GetPrefetchURLLoaderCallCount());
 
   // Shutdown the servers.
   EXPECT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
@@ -146,7 +144,6 @@
 }
 
 IN_PROC_BROWSER_TEST_P(PrefetchBrowserTest, DoublePrefetch) {
-  int target_fetch_count = 0;
   const char* prefetch_path = "/prefetch.html";
   const char* target_path = "/target.html";
   RegisterResponse(prefetch_path, ResponseEntry(base::StringPrintf(
@@ -158,21 +155,21 @@
       ResponseEntry("<head><title>Prefetch Target</title></head>"));
 
   base::RunLoop prefetch_waiter;
-  RegisterRequestMonitor(embedded_test_server(), target_path,
-                         &target_fetch_count, &prefetch_waiter);
+  auto request_counter = RequestCounter::CreateAndMonitor(
+      embedded_test_server(), target_path, &prefetch_waiter);
   RegisterRequestHandler(embedded_test_server());
   ASSERT_TRUE(embedded_test_server()->Start());
-  EXPECT_EQ(0, target_fetch_count);
-  EXPECT_EQ(0, prefetch_url_loader_called_);
+  EXPECT_EQ(0, request_counter->GetRequestCount());
+  EXPECT_EQ(0, GetPrefetchURLLoaderCallCount());
 
   const GURL target_url = embedded_test_server()->GetURL(target_path);
 
   // Loading a page that prefetches the target URL would increment the
-  // |target_fetch_count|, but it should hit only once.
+  // |request_counter|, but it should hit only once.
   NavigateToURL(shell(), embedded_test_server()->GetURL(prefetch_path));
   prefetch_waiter.Run();
-  EXPECT_EQ(1, target_fetch_count);
-  EXPECT_EQ(1, prefetch_url_loader_called_);
+  EXPECT_EQ(1, request_counter->GetRequestCount());
+  EXPECT_EQ(1, GetPrefetchURLLoaderCallCount());
 
   // Shutdown the server.
   EXPECT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
@@ -183,8 +180,6 @@
 }
 
 IN_PROC_BROWSER_TEST_P(PrefetchBrowserTest, NoCacheAndNoStore) {
-  int nocache_fetch_count = 0;
-  int nostore_fetch_count = 0;
   const char* prefetch_path = "/prefetch.html";
   const char* nocache_path = "/target1.html";
   const char* nostore_path = "/target2.html";
@@ -203,41 +198,39 @@
 
   base::RunLoop nocache_waiter;
   base::RunLoop nostore_waiter;
-  RegisterRequestMonitor(embedded_test_server(), nocache_path,
-                         &nocache_fetch_count, &nocache_waiter);
-  RegisterRequestMonitor(embedded_test_server(), nostore_path,
-                         &nostore_fetch_count, &nostore_waiter);
+  auto nocache_request_counter = RequestCounter::CreateAndMonitor(
+      embedded_test_server(), nocache_path, &nocache_waiter);
+  auto nostore_request_counter = RequestCounter::CreateAndMonitor(
+      embedded_test_server(), nostore_path, &nostore_waiter);
   RegisterRequestHandler(embedded_test_server());
   ASSERT_TRUE(embedded_test_server()->Start());
-  EXPECT_EQ(0, prefetch_url_loader_called_);
+  EXPECT_EQ(0, GetPrefetchURLLoaderCallCount());
 
   // Loading a page that prefetches the target URL would increment the
   // fetch count for the both targets.
   NavigateToURL(shell(), embedded_test_server()->GetURL(prefetch_path));
   nocache_waiter.Run();
   nostore_waiter.Run();
-  EXPECT_EQ(1, nocache_fetch_count);
-  EXPECT_EQ(1, nostore_fetch_count);
-  EXPECT_EQ(2, prefetch_url_loader_called_);
+  EXPECT_EQ(1, nocache_request_counter->GetRequestCount());
+  EXPECT_EQ(1, nostore_request_counter->GetRequestCount());
+  EXPECT_EQ(2, GetPrefetchURLLoaderCallCount());
 
   // Subsequent navigation to the no-cache URL wouldn't hit the network, because
   // no-cache resource is kept available up to kPrefetchReuseMins.
   NavigateToURLAndWaitTitle(embedded_test_server()->GetURL(nocache_path),
                             "NoCache Target");
-  EXPECT_EQ(1, nocache_fetch_count);
+  EXPECT_EQ(1, nocache_request_counter->GetRequestCount());
 
   // Subsequent navigation to the no-store URL hit the network again, because
   // no-store resource is not cached even for prefetch.
   NavigateToURLAndWaitTitle(embedded_test_server()->GetURL(nostore_path),
                             "NoStore Target");
-  EXPECT_EQ(2, nostore_fetch_count);
+  EXPECT_EQ(2, nostore_request_counter->GetRequestCount());
 
-  EXPECT_EQ(2, prefetch_url_loader_called_);
+  EXPECT_EQ(2, GetPrefetchURLLoaderCallCount());
 }
 
 IN_PROC_BROWSER_TEST_P(PrefetchBrowserTest, WithPreload) {
-  int target_fetch_count = 0;
-  int preload_fetch_count = 0;
   const char* prefetch_path = "/prefetch.html";
   const char* target_path = "/target.html";
   const char* preload_path = "/preload.js";
@@ -256,23 +249,23 @@
                                  {{"cache-control", "public, max-age=600"}}));
 
   base::RunLoop preload_waiter;
-  RegisterRequestMonitor(embedded_test_server(), target_path,
-                         &target_fetch_count, nullptr /* waiter */);
-  RegisterRequestMonitor(embedded_test_server(), preload_path,
-                         &preload_fetch_count, &preload_waiter);
+  auto target_request_counter =
+      RequestCounter::CreateAndMonitor(embedded_test_server(), target_path);
+  auto preload_request_counter = RequestCounter::CreateAndMonitor(
+      embedded_test_server(), preload_path, &preload_waiter);
   RegisterRequestHandler(embedded_test_server());
   ASSERT_TRUE(embedded_test_server()->Start());
-  EXPECT_EQ(0, prefetch_url_loader_called_);
+  EXPECT_EQ(0, GetPrefetchURLLoaderCallCount());
 
   const GURL target_url = embedded_test_server()->GetURL(target_path);
 
   // Loading a page that prefetches the target URL would increment both
-  // |target_fetch_count| and |preload_fetch_count|.
+  // |target_request_counter| and |preload_request_counter|.
   NavigateToURL(shell(), embedded_test_server()->GetURL(prefetch_path));
   preload_waiter.Run();
-  EXPECT_EQ(1, target_fetch_count);
-  EXPECT_EQ(1, preload_fetch_count);
-  EXPECT_EQ(1, prefetch_url_loader_called_);
+  EXPECT_EQ(1, target_request_counter->GetRequestCount());
+  EXPECT_EQ(1, preload_request_counter->GetRequestCount());
+  EXPECT_EQ(1, GetPrefetchURLLoaderCallCount());
 
   WaitUntilLoaded(embedded_test_server()->GetURL(preload_path));
 
@@ -283,8 +276,6 @@
 }
 
 IN_PROC_BROWSER_TEST_P(PrefetchBrowserTest, CrossOriginWithPreload) {
-  int target_fetch_count = 0;
-  int preload_fetch_count = 0;
   const char* target_path = "/target.html";
   const char* preload_path = "/preload.js";
   RegisterResponse(
@@ -299,11 +290,10 @@
                                  {{"cache-control", "public, max-age=600"}}));
 
   base::RunLoop preload_waiter;
-
-  RegisterRequestMonitor(cross_origin_server_.get(), target_path,
-                         &target_fetch_count, nullptr /* waiter */);
-  RegisterRequestMonitor(cross_origin_server_.get(), preload_path,
-                         &preload_fetch_count, &preload_waiter);
+  auto target_request_counter =
+      RequestCounter::CreateAndMonitor(cross_origin_server_.get(), target_path);
+  auto preload_request_counter = RequestCounter::CreateAndMonitor(
+      cross_origin_server_.get(), preload_path, &preload_waiter);
   RegisterRequestHandler(cross_origin_server_.get());
   ASSERT_TRUE(cross_origin_server_->Start());
 
@@ -316,15 +306,15 @@
                                       cross_origin_target_url.spec().c_str())));
   RegisterRequestHandler(embedded_test_server());
   ASSERT_TRUE(embedded_test_server()->Start());
-  EXPECT_EQ(0, prefetch_url_loader_called_);
+  EXPECT_EQ(0, GetPrefetchURLLoaderCallCount());
 
   // Loading a page that prefetches the target URL would increment both
-  // |target_fetch_count| and |preload_fetch_count|.
+  // |target_request_counter| and |preload_request_counter|.
   NavigateToURL(shell(), embedded_test_server()->GetURL(prefetch_path));
   preload_waiter.Run();
-  EXPECT_EQ(1, target_fetch_count);
-  EXPECT_EQ(1, preload_fetch_count);
-  EXPECT_EQ(1, prefetch_url_loader_called_);
+  EXPECT_EQ(1, target_request_counter->GetRequestCount());
+  EXPECT_EQ(1, preload_request_counter->GetRequestCount());
+  EXPECT_EQ(1, GetPrefetchURLLoaderCallCount());
 
   WaitUntilLoaded(cross_origin_server_->GetURL(preload_path));
 
@@ -337,9 +327,7 @@
   NavigateToURLAndWaitTitle(cross_origin_target_url, "done");
 }
 
-IN_PROC_BROWSER_TEST_P(PrefetchBrowserTest, WebPackageWithPreload) {
-  int target_fetch_count = 0;
-  int preload_fetch_count = 0;
+IN_PROC_BROWSER_TEST_P(PrefetchBrowserTest, SignedExchangeWithPreload) {
   const char* prefetch_path = "/prefetch.html";
   const char* target_sxg_path = "/target.sxg";
   const char* target_path = "/target.html";
@@ -363,13 +351,13 @@
 
   base::RunLoop preload_waiter;
   base::RunLoop prefetch_waiter;
-  RegisterRequestMonitor(embedded_test_server(), target_sxg_path,
-                         &target_fetch_count, &prefetch_waiter);
-  RegisterRequestMonitor(embedded_test_server(), preload_path_in_sxg,
-                         &preload_fetch_count, &preload_waiter);
+  auto target_request_counter = RequestCounter::CreateAndMonitor(
+      embedded_test_server(), target_sxg_path, &prefetch_waiter);
+  auto preload_request_counter = RequestCounter::CreateAndMonitor(
+      embedded_test_server(), preload_path_in_sxg, &preload_waiter);
   RegisterRequestHandler(embedded_test_server());
   ASSERT_TRUE(embedded_test_server()->Start());
-  EXPECT_EQ(0, prefetch_url_loader_called_);
+  EXPECT_EQ(0, GetPrefetchURLLoaderCallCount());
 
   const GURL preload_url_in_sxg =
       embedded_test_server()->GetURL(preload_path_in_sxg);
@@ -384,10 +372,10 @@
   ScopedSignedExchangeHandlerFactory scoped_factory(&factory);
 
   // Loading a page that prefetches the target URL would increment both
-  // |target_fetch_count| and |preload_fetch_count|.
+  // |target_request_counter| and |preload_request_counter|.
   NavigateToURL(shell(), embedded_test_server()->GetURL(prefetch_path));
   prefetch_waiter.Run();
-  EXPECT_EQ(1, target_fetch_count);
+  EXPECT_EQ(1, target_request_counter->GetRequestCount());
 
   // Test after this point requires SignedHTTPExchange support
   if (!GetParam().signed_exchange_enabled)
@@ -396,8 +384,8 @@
   // If the header in the .sxg file is correctly extracted, we should
   // be able to also see the preload.
   preload_waiter.Run();
-  EXPECT_EQ(1, preload_fetch_count);
-  EXPECT_EQ(1, prefetch_url_loader_called_);
+  EXPECT_EQ(1, preload_request_counter->GetRequestCount());
+  EXPECT_EQ(1, GetPrefetchURLLoaderCallCount());
 
   // Shutdown the server.
   EXPECT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
@@ -407,9 +395,8 @@
   NavigateToURLAndWaitTitle(target_sxg_url, "done");
 }
 
-IN_PROC_BROWSER_TEST_P(PrefetchBrowserTest, CrossOriginWebPackageWithPreload) {
-  int target_fetch_count = 0;
-  int preload_fetch_count = 0;
+IN_PROC_BROWSER_TEST_P(PrefetchBrowserTest,
+                       CrossOriginSignedExchangeWithPreload) {
   const char* prefetch_path = "/prefetch.html";
   const char* target_sxg_path = "/target.sxg";
   const char* target_path = "/target.html";
@@ -429,10 +416,10 @@
 
   base::RunLoop preload_waiter;
   base::RunLoop prefetch_waiter;
-  RegisterRequestMonitor(cross_origin_server_.get(), target_sxg_path,
-                         &target_fetch_count, &prefetch_waiter);
-  RegisterRequestMonitor(cross_origin_server_.get(), preload_path_in_sxg,
-                         &preload_fetch_count, &preload_waiter);
+  auto target_request_counter = RequestCounter::CreateAndMonitor(
+      cross_origin_server_.get(), target_sxg_path, &prefetch_waiter);
+  auto preload_request_counter = RequestCounter::CreateAndMonitor(
+      cross_origin_server_.get(), preload_path_in_sxg, &preload_waiter);
   RegisterRequestHandler(cross_origin_server_.get());
   ASSERT_TRUE(cross_origin_server_->Start());
 
@@ -446,7 +433,7 @@
                        target_sxg_url.spec().c_str())));
   RegisterRequestHandler(embedded_test_server());
   ASSERT_TRUE(embedded_test_server()->Start());
-  EXPECT_EQ(0, prefetch_url_loader_called_);
+  EXPECT_EQ(0, GetPrefetchURLLoaderCallCount());
 
   MockSignedExchangeHandlerFactory factory({MockSignedExchangeHandlerParams(
       target_sxg_url, SignedExchangeLoadResult::kSuccess, net::OK,
@@ -457,10 +444,10 @@
   ScopedSignedExchangeHandlerFactory scoped_factory(&factory);
 
   // Loading a page that prefetches the target URL would increment both
-  // |target_fetch_count| and |preload_fetch_count|.
+  // |target_request_counter| and |preload_request_counter|.
   NavigateToURL(shell(), embedded_test_server()->GetURL(prefetch_path));
   prefetch_waiter.Run();
-  EXPECT_EQ(1, target_fetch_count);
+  EXPECT_EQ(1, target_request_counter->GetRequestCount());
 
   // Test after this point requires SignedHTTPExchange support
   if (!GetParam().signed_exchange_enabled)
@@ -468,9 +455,9 @@
   // If the header in the .sxg file is correctly extracted, we should
   // be able to also see the preload.
   preload_waiter.Run();
-  EXPECT_EQ(1, preload_fetch_count);
+  EXPECT_EQ(1, preload_request_counter->GetRequestCount());
 
-  EXPECT_EQ(1, prefetch_url_loader_called_);
+  EXPECT_EQ(1, GetPrefetchURLLoaderCallCount());
 
   WaitUntilLoaded(preload_url_in_sxg);
 
diff --git a/content/browser/loader/prefetch_browsertest_base.cc b/content/browser/loader/prefetch_browsertest_base.cc
index 88c0293..c43b5c4a 100644
--- a/content/browser/loader/prefetch_browsertest_base.cc
+++ b/content/browser/loader/prefetch_browsertest_base.cc
@@ -88,30 +88,16 @@
   return nullptr;
 }
 
-void PrefetchBrowserTestBase::WatchURLAndRunClosure(
-    const std::string& relative_url,
-    int* visit_count,
-    base::OnceClosure closure,
-    const net::test_server::HttpRequest& request) {
-  if (request.relative_url == relative_url) {
-    (*visit_count)++;
-    if (closure)
-      std::move(closure).Run();
-  }
-}
-
 void PrefetchBrowserTestBase::OnPrefetchURLLoaderCalled() {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  base::AutoLock lock(lock_);
   prefetch_url_loader_called_++;
 }
 
-void PrefetchBrowserTestBase::RegisterRequestMonitor(
-    net::EmbeddedTestServer* test_server,
-    const std::string& path,
-    int* count,
-    base::RunLoop* waiter) {
-  test_server->RegisterRequestMonitor(base::BindRepeating(
-      &PrefetchBrowserTestBase::WatchURLAndRunClosure, base::Unretained(this),
-      path, count, waiter ? waiter->QuitClosure() : base::RepeatingClosure()));
+int PrefetchBrowserTestBase::GetPrefetchURLLoaderCallCount() {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  base::AutoLock lock(lock_);
+  return prefetch_url_loader_called_;
 }
 
 void PrefetchBrowserTestBase::RegisterRequestHandler(
@@ -157,4 +143,40 @@
   ASSERT_TRUE(result);
 }
 
+// static
+scoped_refptr<PrefetchBrowserTestBase::RequestCounter>
+PrefetchBrowserTestBase::RequestCounter::CreateAndMonitor(
+    net::EmbeddedTestServer* test_server,
+    const std::string& path,
+    base::RunLoop* waiter) {
+  auto counter = base::MakeRefCounted<RequestCounter>(path, waiter);
+  test_server->RegisterRequestMonitor(
+      base::BindRepeating(&RequestCounter::OnRequest, counter));
+  return counter;
+}
+
+PrefetchBrowserTestBase::RequestCounter::RequestCounter(const std::string& path,
+                                                        base::RunLoop* waiter)
+    : waiter_closure_(waiter ? waiter->QuitClosure() : base::OnceClosure()),
+      path_(path) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+PrefetchBrowserTestBase::RequestCounter::~RequestCounter() = default;
+
+int PrefetchBrowserTestBase::RequestCounter::GetRequestCount() {
+  base::AutoLock lock(lock_);
+  return request_count_;
+}
+
+void PrefetchBrowserTestBase::RequestCounter::OnRequest(
+    const net::test_server::HttpRequest& request) {
+  if (request.relative_url != path_)
+    return;
+  base::AutoLock lock(lock_);
+  ++request_count_;
+  if (waiter_closure_)
+    std::move(waiter_closure_).Run();
+}
+
 }  // namespace content
diff --git a/content/browser/loader/prefetch_browsertest_base.h b/content/browser/loader/prefetch_browsertest_base.h
index eb478ea..4050076 100644
--- a/content/browser/loader/prefetch_browsertest_base.h
+++ b/content/browser/loader/prefetch_browsertest_base.h
@@ -10,6 +10,9 @@
 
 #include "base/callback.h"
 #include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "base/thread_annotations.h"
 #include "content/public/test/content_browser_test.h"
 #include "net/test/embedded_test_server/http_request.h"
 #include "net/test/embedded_test_server/http_response.h"
@@ -56,30 +59,54 @@
   void SetUpOnMainThread() override;
 
  protected:
+  class RequestCounter : public base::RefCountedThreadSafe<RequestCounter> {
+   public:
+    // Create a counter that is to be incremented when |path| on the
+    // |test_server| is accessed. |waiter| can be optionally specified that will
+    // be run after the counter is incremented. The counter value can be
+    // obtained via GetRequestCount(). This class works across threads,
+    // GetRequestCount can be called on any threads.
+    static scoped_refptr<RequestCounter> CreateAndMonitor(
+        net::EmbeddedTestServer* test_server,
+        const std::string& path,
+        base::RunLoop* waiter = nullptr);
+    RequestCounter(const std::string& path, base::RunLoop* waiter);
+
+    int GetRequestCount();
+
+   private:
+    friend base::RefCountedThreadSafe<RequestCounter>;
+    ~RequestCounter();
+
+    void OnRequest(const net::test_server::HttpRequest& request);
+
+    base::OnceClosure waiter_closure_;
+    const std::string path_;
+    int request_count_ GUARDED_BY(lock_) = 0;
+    base::Lock lock_;
+
+    DISALLOW_COPY_AND_ASSIGN(RequestCounter);
+  };
+
   void RegisterResponse(const std::string& url, ResponseEntry&& entry);
 
   std::unique_ptr<net::test_server::HttpResponse> ServeResponses(
       const net::test_server::HttpRequest& request);
-  void WatchURLAndRunClosure(const std::string& relative_url,
-                             int* visit_count,
-                             base::OnceClosure closure,
-                             const net::test_server::HttpRequest& request);
   void OnPrefetchURLLoaderCalled();
-  void RegisterRequestMonitor(net::test_server::EmbeddedTestServer* test_server,
-                              const std::string& path,
-                              int* count,
-                              base::RunLoop* waiter);
 
   void RegisterRequestHandler(
       net::test_server::EmbeddedTestServer* test_server);
   void NavigateToURLAndWaitTitle(const GURL& url, const std::string& title);
   void WaitUntilLoaded(const GURL& url);
 
-  int prefetch_url_loader_called_ = 0;
+  int GetPrefetchURLLoaderCallCount();
 
  private:
   std::map<std::string, ResponseEntry> response_map_;
 
+  int prefetch_url_loader_called_ GUARDED_BY(lock_) = 0;
+  base::Lock lock_;
+
   DISALLOW_COPY_AND_ASSIGN(PrefetchBrowserTestBase);
 };
 
diff --git a/content/browser/navigation_browsertest.cc b/content/browser/navigation_browsertest.cc
index dc7ce0c..e119d8b 100644
--- a/content/browser/navigation_browsertest.cc
+++ b/content/browser/navigation_browsertest.cc
@@ -1763,7 +1763,7 @@
   shell()->LoadURL(embedded_test_server()->GetURL("/doc"));
   response_1.WaitForRequest();
   EXPECT_FALSE(
-      base::ContainsKey(response_1.http_request()->headers, "header_name"));
+      base::Contains(response_1.http_request()->headers, "header_name"));
   response_1.Send(
       "HTTP/1.1 302 Moved Temporarily\r\nLocation: /new_doc\r\n\r\n");
   response_1.Done();
@@ -1859,7 +1859,7 @@
   // 2) The header is removed from the second request after the redirect.
   response_2.WaitForRequest();
   EXPECT_FALSE(
-      base::ContainsKey(response_2.http_request()->headers, "header_name"));
+      base::Contains(response_2.http_request()->headers, "header_name"));
 }
 
 struct NewWebContentsData {
diff --git a/content/browser/network_service_client.cc b/content/browser/network_service_client.cc
index f988069..82b6868 100644
--- a/content/browser/network_service_client.cc
+++ b/content/browser/network_service_client.cc
@@ -354,9 +354,12 @@
     const base::Optional<network::ResourceResponseHead>& head,
     network::mojom::AuthChallengeResponderPtr auth_challenge_responder,
     FrameTreeNodeIdRegistry::IsMainFrameGetter is_main_frame_getter) {
-  // |is_main_frame_getter| should not be a null callback because the
-  // FrameTreeNodeIdRegistry should have a corresponding FrameTreeNode id.
-  CHECK(is_main_frame_getter);
+  if (!is_main_frame_getter) {
+    // FrameTreeNode id may already be removed from FrameTreeNodeIdRegistry
+    // due to thread hopping.
+    std::move(auth_challenge_responder)->OnAuthCredentials(base::nullopt);
+    return;
+  }
   base::Optional<bool> is_main_frame_opt = is_main_frame_getter.Run();
   // The frame may already be gone due to thread hopping.
   if (!is_main_frame_opt) {
diff --git a/content/browser/plugin_list.cc b/content/browser/plugin_list.cc
index 30d8862..466f607 100644
--- a/content/browser/plugin_list.cc
+++ b/content/browser/plugin_list.cc
@@ -188,7 +188,7 @@
   }
 
   for (const base::FilePath& path : extra_plugin_paths) {
-    if (base::ContainsValue(*plugin_paths, path))
+    if (base::Contains(*plugin_paths, path))
       continue;
     plugin_paths->push_back(path);
   }
diff --git a/content/browser/plugin_private_storage_helper.cc b/content/browser/plugin_private_storage_helper.cc
index e9973be..9821dced 100644
--- a/content/browser/plugin_private_storage_helper.cc
+++ b/content/browser/plugin_private_storage_helper.cc
@@ -418,7 +418,7 @@
   if (!storage_origin.is_empty()) {
     DCHECK(origin_matcher.is_null()) << "Only 1 of |storage_origin| and "
                                         "|origin_matcher| should be specified.";
-    if (!base::ContainsKey(origins, storage_origin)) {
+    if (!base::Contains(origins, storage_origin)) {
       // Nothing matches, so nothing to do.
       callback.Run();
       return;
diff --git a/content/browser/portal/portal_browsertest.cc b/content/browser/portal/portal_browsertest.cc
index 729d342..88743034 100644
--- a/content/browser/portal/portal_browsertest.cc
+++ b/content/browser/portal/portal_browsertest.cc
@@ -11,6 +11,7 @@
 #include "content/browser/frame_host/render_frame_host_manager.h"
 #include "content/browser/frame_host/render_frame_proxy_host.h"
 #include "content/browser/portal/portal.h"
+#include "content/browser/renderer_host/input/synthetic_tap_gesture.h"
 #include "content/browser/renderer_host/render_widget_host_input_event_router.h"
 #include "content/browser/renderer_host/render_widget_host_view_child_frame.h"
 #include "content/browser/web_contents/web_contents_impl.h"
@@ -245,7 +246,7 @@
   EXPECT_NE(nullptr, portal_contents);
   EXPECT_NE(portal_contents->GetLastCommittedURL(), a_url);
 
-  // The portal should not have navigated yet, we can observe the Portal's
+  // The portal should not have navigated yet, so we can observe the Portal's
   // first navigation.
   TestNavigationObserver navigation_observer(portal_contents);
   navigation_observer.Wait();
@@ -536,6 +537,53 @@
   EXPECT_EQ(base::nullopt, kill_waiter.Wait());
 }
 
+// Regression test for crbug.com/969714. Tests that receiving a touch ack
+// from the predecessor after portal activation doesn't cause a crash.
+IN_PROC_BROWSER_TEST_F(PortalBrowserTest, TouchAckAfterActivate) {
+  EXPECT_TRUE(NavigateToURL(
+      shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
+  WebContentsImpl* web_contents_impl =
+      static_cast<WebContentsImpl*>(shell()->web_contents());
+  RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
+
+  // Create portal and wait for navigation.
+  PortalCreatedObserver portal_created_observer(main_frame);
+  GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  EXPECT_TRUE(ExecJs(
+      main_frame, JsReplace("var portal = document.createElement('portal');"
+                            "portal.src = $1;"
+                            "document.body.appendChild(portal);"
+                            "document.body.addEventListener('touchstart', "
+                            "e => { portal.activate(); }, {passive: false});",
+                            a_url)));
+  Portal* portal = portal_created_observer.WaitUntilPortalCreated();
+  WebContentsImpl* portal_contents = portal->GetPortalContents();
+
+  // The portal should not have navigated yet, wait for the first navigation.
+  TestNavigationObserver navigation_observer(portal_contents);
+  navigation_observer.Wait();
+
+  PortalInterceptorForTesting* portal_interceptor =
+      PortalInterceptorForTesting::From(portal);
+  RenderWidgetHostImpl* render_widget_host = main_frame->GetRenderWidgetHost();
+  InputEventAckWaiter input_event_ack_waiter(
+      render_widget_host, blink::WebInputEvent::Type::kTouchStart);
+
+  SyntheticTapGestureParams params;
+  params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT;
+  params.position = gfx::PointF(20, 20);
+
+  std::unique_ptr<SyntheticTapGesture> gesture =
+      std::make_unique<SyntheticTapGesture>(params);
+  render_widget_host->QueueSyntheticGesture(
+      std::move(gesture), base::Bind([](SyntheticGesture::Result) {}));
+  portal_interceptor->WaitForActivate();
+  EXPECT_EQ(portal_contents, shell()->web_contents());
+
+  // Wait for a touch ack to be sent from the predecessor.
+  input_event_ack_waiter.Wait();
+}
+
 class PortalOOPIFBrowserTest : public PortalBrowserTest {
  protected:
   PortalOOPIFBrowserTest() {}
diff --git a/content/browser/posix_file_descriptor_info_impl.cc b/content/browser/posix_file_descriptor_info_impl.cc
index fc76d25..94a498a9 100644
--- a/content/browser/posix_file_descriptor_info_impl.cc
+++ b/content/browser/posix_file_descriptor_info_impl.cc
@@ -66,7 +66,7 @@
 }
 
 bool PosixFileDescriptorInfoImpl::OwnsFD(base::PlatformFile file) {
-  return base::ContainsValue(owned_descriptors_, file);
+  return base::Contains(owned_descriptors_, file);
 }
 
 base::ScopedFD PosixFileDescriptorInfoImpl::ReleaseFD(base::PlatformFile file) {
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index 8b87570b..6415dc8 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -2271,11 +2271,9 @@
 }
 
 void RenderWidgetHostImpl::DidAllocateSharedBitmap(
-    mojo::ScopedSharedBufferHandle buffer,
+    base::ReadOnlySharedMemoryRegion region,
     const viz::SharedBitmapId& id) {
-  if (!shared_bitmap_manager_->ChildAllocatedSharedBitmap(
-          viz::bitmap_allocation::FromMojoHandle(std::move(buffer)).Map(),
-          id)) {
+  if (!shared_bitmap_manager_->ChildAllocatedSharedBitmap(region.Map(), id)) {
     bad_message::ReceivedBadMessage(GetProcess(),
                                     bad_message::RWH_SHARED_BITMAP);
   }
@@ -2710,6 +2708,12 @@
 
   auto* input_event_router =
       delegate() ? delegate()->GetInputEventRouter() : nullptr;
+  // With portals, if a touch event triggers an activation, it is possible to
+  // receive a touch ack after activation. The view is destroyed on activation
+  // and any pending events in the touch ack queue have already been cleared, so
+  // we just ignore this ack.
+  if (!view_)
+    return;
 
   // At present interstitial pages might not have an input event router, so we
   // just have the view process the ack directly in that case; the view is
@@ -2717,7 +2721,7 @@
   // ProcessAckedTouchEvent().
   if (input_event_router)
     input_event_router->ProcessAckedTouchEvent(event, ack_result, view_.get());
-  else if (view_)
+  else
     view_->ProcessAckedTouchEvent(event, ack_result);
 }
 
diff --git a/content/browser/renderer_host/render_widget_host_impl.h b/content/browser/renderer_host/render_widget_host_impl.h
index 654c60e..067b75f 100644
--- a/content/browser/renderer_host/render_widget_host_impl.h
+++ b/content/browser/renderer_host/render_widget_host_impl.h
@@ -21,7 +21,7 @@
 #include "base/containers/queue.h"
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
-#include "base/memory/shared_memory_handle.h"
+#include "base/memory/read_only_shared_memory_region.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/optional.h"
@@ -656,7 +656,7 @@
       uint64_t submit_time,
       const SubmitCompositorFrameSyncCallback callback) override;
   void DidNotProduceFrame(const viz::BeginFrameAck& ack) override;
-  void DidAllocateSharedBitmap(mojo::ScopedSharedBufferHandle buffer,
+  void DidAllocateSharedBitmap(base::ReadOnlySharedMemoryRegion region,
                                const viz::SharedBitmapId& id) override;
   void DidDeleteSharedBitmap(const viz::SharedBitmapId& id) override;
 
diff --git a/content/browser/scheduler/browser_task_executor.h b/content/browser/scheduler/browser_task_executor.h
index e72e31a..f37dac3 100644
--- a/content/browser/scheduler/browser_task_executor.h
+++ b/content/browser/scheduler/browser_task_executor.h
@@ -62,6 +62,8 @@
   //
   // Attention: This method can only be called once (as there must be only one
   // IO thread).
+  // Attention: Must be called after Create()
+  // Attention: Can not be called after Shutdown() or ResetForTesting()
   static std::unique_ptr<BrowserProcessSubThread> CreateIOThread();
 
   // Enables non best effort queues on the IO thread. Usually called from
@@ -83,7 +85,9 @@
 
   // Winds down the BrowserTaskExecutor, after this no tasks can be executed
   // and the base::TaskExecutor APIs are non-functional but won't crash if
-  // called.
+  // called. In unittests however we need to clean up, so
+  // BrowserTaskExecutor::ResetForTesting should be
+  // called (~TestBrowserThreadBundle() takes care of this).
   static void Shutdown();
 
   // Unregister and delete the TaskExecutor after a test.
diff --git a/content/browser/service_worker/service_worker_database.h b/content/browser/service_worker/service_worker_database.h
index 0d26258..e3c87c18 100644
--- a/content/browser/service_worker/service_worker_database.h
+++ b/content/browser/service_worker/service_worker_database.h
@@ -94,15 +94,31 @@
   };
 
   struct ResourceRecord {
+    // Represents an error state. Each enum instance should be a negative
+    // value.  This is just temporary for debugging.
+    // TODO(hayato): Remove this once we fix crbug.com/946719.
+    enum class ErrorState : int64_t {
+      // We don't use -1 here to catch an untracked usage of -1 as an error
+      // code.
+      kStartedCaching = -2,
+      kFinishedCachingNoBytesWritten = -3,
+    };
+
     int64_t resource_id;
     GURL url;
-    // Signed so we can store -1 to specify an unknown or error state.  When
-    // stored to the database, this value should always be >= 0.
+    // Signed so we can store ErrorState. When stored to the database, this
+    // value should always be >= 0.
     int64_t size_bytes;
 
     ResourceRecord() : resource_id(-1), size_bytes(0) {}
     ResourceRecord(int64_t id, GURL url, int64_t size_bytes)
-        : resource_id(id), url(url), size_bytes(size_bytes) {}
+        : resource_id(id), url(url), size_bytes(size_bytes) {
+      DCHECK_GE(size_bytes, 0);
+    }
+    ResourceRecord(int64_t id, GURL url, ErrorState error_state)
+        : resource_id(id),
+          url(url),
+          size_bytes(static_cast<int64_t>(error_state)) {}
   };
 
   // Reads next available ids from the database. Returns OK if they are
diff --git a/content/browser/service_worker/service_worker_script_cache_map.cc b/content/browser/service_worker/service_worker_script_cache_map.cc
index d568d80..62c79ed 100644
--- a/content/browser/service_worker/service_worker_script_cache_map.cc
+++ b/content/browser/service_worker/service_worker_script_cache_map.cc
@@ -42,8 +42,9 @@
       << owner_->status();
   if (!context_)
     return;  // Our storage has been wiped via DeleteAndStartOver.
-  resource_map_[url] =
-      ServiceWorkerDatabase::ResourceRecord(resource_id, url, -1);
+  resource_map_[url] = ServiceWorkerDatabase::ResourceRecord(
+      resource_id, url,
+      ServiceWorkerDatabase::ResourceRecord::ErrorState::kStartedCaching);
   context_->storage()->StoreUncommittedResourceId(resource_id);
 }
 
@@ -66,8 +67,12 @@
       main_script_status_ = net::URLRequestStatus::FromError(net_error);
       main_script_status_message_ = status_message;
     }
-  } else {
+  } else if (size_bytes >= 0) {
     resource_map_[url].size_bytes = size_bytes;
+  } else {
+    resource_map_[url].size_bytes =
+        static_cast<int64_t>(ServiceWorkerDatabase::ResourceRecord::ErrorState::
+                                 kFinishedCachingNoBytesWritten);
   }
 }
 
diff --git a/content/browser/service_worker/service_worker_single_script_update_checker.cc b/content/browser/service_worker/service_worker_single_script_update_checker.cc
index c300cf105..89bbc04 100644
--- a/content/browser/service_worker/service_worker_single_script_update_checker.cc
+++ b/content/browser/service_worker/service_worker_single_script_update_checker.cc
@@ -345,17 +345,6 @@
     return;
   }
 
-  // Response body is empty.
-  if (network_loader_state_ ==
-          ServiceWorkerNewScriptLoader::NetworkLoaderState::kCompleted &&
-      body_writer_state_ ==
-          ServiceWorkerNewScriptLoader::WriterState::kCompleted) {
-    // Compare the cached data with an empty data to notify |cache_writer_|
-    // the end of the comparison.
-    CompareData(nullptr /* pending_buffer */, 0 /* bytes_available */);
-    return;
-  }
-
   MaybeStartNetworkConsumerHandleWatcher();
 }
 
@@ -422,9 +411,14 @@
   NOTREACHED() << static_cast<int>(result);
 }
 
+// |pending_buffer| is a buffer keeping a Mojo data pipe which is going to be
+// read by a cache writer. It should be kept alive until the read is done. It's
+// nullptr when there is no data to be read, and that means the body from the
+// network reaches the end. In that case, |bytes_to_compare| is zero.
 void ServiceWorkerSingleScriptUpdateChecker::CompareData(
     scoped_refptr<network::MojoToNetPendingBuffer> pending_buffer,
     uint32_t bytes_to_compare) {
+  DCHECK(pending_buffer || bytes_to_compare == 0);
   auto buffer = base::MakeRefCounted<WrappedIOBuffer>(
       pending_buffer ? pending_buffer->buffer() : nullptr);
 
@@ -435,11 +429,6 @@
           &ServiceWorkerSingleScriptUpdateChecker::OnCompareDataComplete,
           weak_factory_.GetWeakPtr(), pending_buffer, bytes_to_compare));
 
-  if (pending_buffer) {
-    pending_buffer->CompleteRead(bytes_to_compare);
-    network_consumer_ = pending_buffer->ReleaseHandle();
-  }
-
   if (error == net::ERR_IO_PENDING && !cache_writer_->is_pausing()) {
     // OnCompareDataComplete() will be called asynchronously.
     return;
@@ -449,10 +438,25 @@
   OnCompareDataComplete(std::move(pending_buffer), bytes_to_compare, error);
 }
 
+// |pending_buffer| is a buffer passed from CompareData(). Please refer to the
+// comment on CompareData(). |error| is the result of the comparison done by the
+// cache writer (which is actually reading and not yet writing to the cache,
+// since it's in the comparison phase). It's net::OK when the body from the
+// network and from the disk cache are the same, net::ERR_IO_PENDING if it
+// detects a change in the script, or other error code if something went wrong
+// reading from the disk cache.
 void ServiceWorkerSingleScriptUpdateChecker::OnCompareDataComplete(
     scoped_refptr<network::MojoToNetPendingBuffer> pending_buffer,
     uint32_t bytes_written,
     net::Error error) {
+  DCHECK(pending_buffer || bytes_written == 0);
+  if (pending_buffer) {
+    // We consumed |bytes_written| bytes of data from the network so call
+    // CompleteRead(), regardless of what |error| is.
+    pending_buffer->CompleteRead(bytes_written);
+    network_consumer_ = pending_buffer->ReleaseHandle();
+  }
+
   if (cache_writer_->is_pausing()) {
     // |cache_writer_| can be pausing only when it finds difference between
     // stored body and network body.
@@ -460,11 +464,21 @@
     Succeed(Result::kDifferent);
     return;
   }
-  if (!pending_buffer || error != net::OK) {
+
+  if (error != net::OK) {
+    // Something went wrong reading from the disk cache.
+    Fail(blink::ServiceWorkerStatusCode::kErrorDiskCache,
+         kServiceWorkerFetchScriptError);
+    return;
+  }
+
+  if (bytes_written == 0) {
+    // All data has been read. If we reach here without any error, the script
+    // from the network was identical to the one in the disk cache.
     Succeed(Result::kIdentical);
     return;
   }
-  DCHECK(pending_buffer);
+
   network_watcher_.ArmOrNotify();
 }
 
diff --git a/content/browser/service_worker/service_worker_single_script_update_checker_unittest.cc b/content/browser/service_worker/service_worker_single_script_update_checker_unittest.cc
index 0d07db7..94881e8 100644
--- a/content/browser/service_worker/service_worker_single_script_update_checker_unittest.cc
+++ b/content/browser/service_worker/service_worker_single_script_update_checker_unittest.cc
@@ -16,6 +16,7 @@
 #include "net/base/load_flags.h"
 #include "net/http/http_util.h"
 #include "services/network/test/test_url_loader_factory.h"
+#include "services/network/test/test_utils.h"
 
 namespace content {
 namespace {
@@ -154,7 +155,19 @@
   DISALLOW_COPY_AND_ASSIGN(ServiceWorkerSingleScriptUpdateCheckerTest);
 };
 
-TEST_F(ServiceWorkerSingleScriptUpdateCheckerTest, Identical_SingleSyncRead) {
+class ServiceWorkerSingleScriptUpdateCheckerToggleAsyncTest
+    : public ServiceWorkerSingleScriptUpdateCheckerTest,
+      public testing::WithParamInterface<bool> {
+ public:
+  static bool IsAsync() { return GetParam(); }
+};
+
+INSTANTIATE_TEST_SUITE_P(ServiceWorkerSingleScriptUpdateCheckerToggleAsyncTestP,
+                         ServiceWorkerSingleScriptUpdateCheckerToggleAsyncTest,
+                         testing::Bool());
+
+TEST_P(ServiceWorkerSingleScriptUpdateCheckerToggleAsyncTest,
+       Identical_SingleRead) {
   // Response body from the network.
   const std::string body_from_net("abcdef");
 
@@ -170,7 +183,7 @@
   auto writer = std::make_unique<MockServiceWorkerResponseWriter>();
   MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get();
   compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage),
-                               false /* async */);
+                               IsAsync());
 
   base::Optional<CheckResult> check_result;
   std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker =
@@ -178,6 +191,22 @@
           kScriptURL, GURL(kScope), std::move(compare_reader),
           std::move(copy_reader), std::move(writer), loader_factory.get(),
           &check_result);
+
+  if (IsAsync()) {
+    // Blocked on reading the header.
+    base::RunLoop().RunUntilIdle();
+    EXPECT_FALSE(check_result.has_value());
+
+    // Unblock the header, and then blocked on reading the body.
+    compare_reader_rawptr->CompletePendingRead();
+    base::RunLoop().RunUntilIdle();
+    EXPECT_FALSE(check_result.has_value());
+
+    // Unblock the body.
+    compare_reader_rawptr->CompletePendingRead();
+  }
+
+  // Complete the comparison of the body. It should be identical.
   base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(check_result.has_value());
   EXPECT_EQ(check_result.value().result,
@@ -186,12 +215,13 @@
   EXPECT_TRUE(compare_reader_rawptr->AllExpectedReadsDone());
 }
 
-TEST_F(ServiceWorkerSingleScriptUpdateCheckerTest, Different_SingleSyncRead) {
+TEST_P(ServiceWorkerSingleScriptUpdateCheckerToggleAsyncTest,
+       Identical_MultipleRead) {
   // Response body from the network.
   const std::string body_from_net("abcdef");
 
   // Stored data for |kScriptURL|.
-  const std::vector<std::string> body_from_storage{"abxx"};
+  const std::vector<std::string> body_from_storage{"abc", "def"};
 
   std::unique_ptr<network::TestURLLoaderFactory> loader_factory =
       CreateLoaderFactoryWithRespone(GURL(kScriptURL), kSuccessHeader,
@@ -202,7 +232,7 @@
   auto writer = std::make_unique<MockServiceWorkerResponseWriter>();
   MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get();
   compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage),
-                               false /* async */);
+                               IsAsync());
 
   base::Optional<CheckResult> check_result;
   std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker =
@@ -211,121 +241,37 @@
           std::move(copy_reader), std::move(writer), loader_factory.get(),
           &check_result);
 
+  if (IsAsync()) {
+    // Blocked on reading the header.
+    base::RunLoop().RunUntilIdle();
+    EXPECT_FALSE(check_result.has_value());
+
+    // Unblock the header, and then blocked on reading the body.
+    compare_reader_rawptr->CompletePendingRead();
+    base::RunLoop().RunUntilIdle();
+    EXPECT_FALSE(check_result.has_value());
+
+    // Unblock the body ("abc").
+    compare_reader_rawptr->CompletePendingRead();
+    base::RunLoop().RunUntilIdle();
+    EXPECT_FALSE(check_result.has_value());
+
+    // Unblock the body ("def").
+    compare_reader_rawptr->CompletePendingRead();
+  }
+
+  // Complete the comparison of the body. It should be identical.
   base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(check_result.has_value());
   EXPECT_EQ(check_result.value().result,
-            ServiceWorkerSingleScriptUpdateChecker::Result::kDifferent);
+            ServiceWorkerSingleScriptUpdateChecker::Result::kIdentical);
   EXPECT_EQ(check_result.value().url, kScriptURL);
   EXPECT_TRUE(compare_reader_rawptr->AllExpectedReadsDone());
 }
 
-TEST_F(ServiceWorkerSingleScriptUpdateCheckerTest, Different_MultipleSyncRead) {
-  // Response body from the network.
-  const std::string body_from_net("abcdef");
-
-  // Stored data for |kScriptURL| (the data for compare reader).
-  // The comparison should stop in the second block of data.
-  const std::vector<std::string> body_from_storage{"ab", "cx"};
-
-  std::unique_ptr<network::TestURLLoaderFactory> loader_factory =
-      CreateLoaderFactoryWithRespone(GURL(kScriptURL), kSuccessHeader,
-                                     body_from_net, net::OK);
-
-  auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>();
-  auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>();
-  auto writer = std::make_unique<MockServiceWorkerResponseWriter>();
-  MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get();
-  compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage),
-                               false /* async */);
-
-  base::Optional<CheckResult> check_result;
-  std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker =
-      CreateSingleScriptUpdateCheckerWithoutHttpCache(
-          kScriptURL, GURL(kScope), std::move(compare_reader),
-          std::move(copy_reader), std::move(writer), loader_factory.get(),
-          &check_result);
-
-  base::RunLoop().RunUntilIdle();
-  EXPECT_TRUE(check_result.has_value());
-  EXPECT_EQ(check_result.value().result,
-            ServiceWorkerSingleScriptUpdateChecker::Result::kDifferent);
-  EXPECT_EQ(check_result.value().url, kScriptURL);
-  EXPECT_TRUE(compare_reader_rawptr->AllExpectedReadsDone());
-}
-
-TEST_F(ServiceWorkerSingleScriptUpdateCheckerTest, NetworkDataLong_SyncRead) {
-  // Response body from the network.
-  const std::string body_from_net("abcdef");
-
-  // Stored data for |kScriptURL| (the data for compare reader).
-  const std::vector<std::string> body_from_storage{"ab", "cd", ""};
-
-  std::unique_ptr<network::TestURLLoaderFactory> loader_factory =
-      CreateLoaderFactoryWithRespone(GURL(kScriptURL), kSuccessHeader,
-                                     body_from_net, net::OK);
-
-  auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>();
-  auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>();
-  auto writer = std::make_unique<MockServiceWorkerResponseWriter>();
-  MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get();
-  compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage),
-                               false /* async */);
-
-  base::Optional<CheckResult> check_result;
-  std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker =
-      CreateSingleScriptUpdateCheckerWithoutHttpCache(
-          kScriptURL, GURL(kScope), std::move(compare_reader),
-          std::move(copy_reader), std::move(writer), loader_factory.get(),
-          &check_result);
-
-  base::RunLoop().RunUntilIdle();
-  EXPECT_TRUE(check_result.has_value());
-  EXPECT_EQ(check_result.value().result,
-            ServiceWorkerSingleScriptUpdateChecker::Result::kDifferent);
-  EXPECT_EQ(check_result.value().url, kScriptURL);
-  EXPECT_TRUE(compare_reader_rawptr->AllExpectedReadsDone());
-}
-
-TEST_F(ServiceWorkerSingleScriptUpdateCheckerTest, NetworkDataShort_SyncRead) {
-  // Response body from the network.
-  const std::string body_from_net("abcdef");
-
-  // Stored data for |kScriptURL| (the data for compare reader).
-  const std::vector<std::string> body_in_storage{"ab", "cd", "ef", "gh"};
-
-  // Stored data that will actually be read from the compare reader.
-  // The last 2 bytes of |body_in_storage| won't be read.
-  const std::vector<std::string> body_read_from_storage{"ab", "cd", "ef"};
-
-  std::unique_ptr<network::TestURLLoaderFactory> loader_factory =
-      CreateLoaderFactoryWithRespone(GURL(kScriptURL), kSuccessHeader,
-                                     body_from_net, net::OK);
-
-  auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>();
-  auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>();
-  auto writer = std::make_unique<MockServiceWorkerResponseWriter>();
-  MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get();
-  compare_reader->ExpectReadOk(body_read_from_storage,
-                               TotalBytes(body_in_storage), false /* async */);
-
-  base::Optional<CheckResult> check_result;
-  std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker =
-      CreateSingleScriptUpdateCheckerWithoutHttpCache(
-          kScriptURL, GURL(kScope), std::move(compare_reader),
-          std::move(copy_reader), std::move(writer), loader_factory.get(),
-          &check_result);
-
-  base::RunLoop().RunUntilIdle();
-  EXPECT_TRUE(check_result.has_value());
-  EXPECT_EQ(check_result.value().result,
-            ServiceWorkerSingleScriptUpdateChecker::Result::kDifferent);
-  EXPECT_EQ(check_result.value().url, kScriptURL);
-  EXPECT_TRUE(compare_reader_rawptr->AllExpectedReadsDone());
-}
-
-TEST_F(ServiceWorkerSingleScriptUpdateCheckerTest, Identical_SingleAsyncRead) {
-  // Response body from the network.
-  const std::string body_from_net("abcdef");
+TEST_P(ServiceWorkerSingleScriptUpdateCheckerToggleAsyncTest, Identical_Empty) {
+  // Response body from the network, which is empty.
+  const std::string body_from_net("");
 
   // Stored data for |kScriptURL| (the data for compare reader).
   const std::vector<std::string> body_from_storage{body_from_net};
@@ -339,7 +285,7 @@
   auto writer = std::make_unique<MockServiceWorkerResponseWriter>();
   MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get();
   compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage),
-                               true /* async */);
+                               IsAsync());
 
   base::Optional<CheckResult> check_result;
   std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker =
@@ -348,29 +294,438 @@
           std::move(copy_reader), std::move(writer), loader_factory.get(),
           &check_result);
 
-  // Update check stops in WriteHeader() due to the asynchronous read of the
-  // |compare_reader|.
-  base::RunLoop().RunUntilIdle();
-  EXPECT_FALSE(check_result.has_value());
+  if (IsAsync()) {
+    // Blocked on reading the header.
+    base::RunLoop().RunUntilIdle();
+    EXPECT_FALSE(check_result.has_value());
 
-  // Continue the update check and trigger OnWriteHeadersComplete(). The resumed
-  // update check stops again at CompareData().
-  compare_reader_rawptr->CompletePendingRead();
-  base::RunLoop().RunUntilIdle();
-  EXPECT_FALSE(check_result.has_value());
+    // Unblock the header. The initial block of the network body is empty, and
+    // the empty body is passed to the cache writer. It will finish the
+    // comparison immediately.
+    compare_reader_rawptr->CompletePendingRead();
+  }
 
-  // Continue the update check and trigger OnCompareDataComplete(). This will
-  // finish the entire update check.
-  compare_reader_rawptr->CompletePendingRead();
+  // Both network and storage are empty. The result should be kIdentical.
   base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(check_result.has_value());
   EXPECT_EQ(check_result.value().result,
             ServiceWorkerSingleScriptUpdateChecker::Result::kIdentical);
   EXPECT_EQ(check_result.value().url, kScriptURL);
   EXPECT_FALSE(check_result.value().paused_state);
+}
+
+TEST_P(ServiceWorkerSingleScriptUpdateCheckerToggleAsyncTest,
+       Different_SingleRead_NetworkIsLonger) {
+  // Response body from the network.
+  const std::string body_from_net = "abcdef";
+
+  // Stored data for |kScriptURL|.
+  const std::vector<std::string> body_from_storage{"abc", ""};
+
+  std::unique_ptr<network::TestURLLoaderFactory> loader_factory =
+      CreateLoaderFactoryWithRespone(GURL(kScriptURL), kSuccessHeader,
+                                     body_from_net, net::OK);
+
+  auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>();
+  auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>();
+  auto writer = std::make_unique<MockServiceWorkerResponseWriter>();
+  MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get();
+  compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage),
+                               IsAsync());
+
+  base::Optional<CheckResult> check_result;
+  std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker =
+      CreateSingleScriptUpdateCheckerWithoutHttpCache(
+          kScriptURL, GURL(kScope), std::move(compare_reader),
+          std::move(copy_reader), std::move(writer), loader_factory.get(),
+          &check_result);
+
+  if (IsAsync()) {
+    // Blocked on reading the header.
+    base::RunLoop().RunUntilIdle();
+    EXPECT_FALSE(check_result.has_value());
+
+    // Unblock the header, and then blocked on reading the body.
+    compare_reader_rawptr->CompletePendingRead();
+    base::RunLoop().RunUntilIdle();
+    EXPECT_FALSE(check_result.has_value());
+
+    // Unblock the body ("abc").
+    compare_reader_rawptr->CompletePendingRead();
+    base::RunLoop().RunUntilIdle();
+    EXPECT_FALSE(check_result.has_value());
+
+    // Unblock the body from storage (""). The cache writer detects the end of
+    // the body from the disk cache.
+    compare_reader_rawptr->CompletePendingRead();
+  }
+
+  // Complete the comparison of the body. It should be different.
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(check_result.has_value());
+  EXPECT_EQ(check_result.value().result,
+            ServiceWorkerSingleScriptUpdateChecker::Result::kDifferent);
+  EXPECT_EQ(check_result.value().url, kScriptURL);
   EXPECT_TRUE(compare_reader_rawptr->AllExpectedReadsDone());
 }
 
+TEST_P(ServiceWorkerSingleScriptUpdateCheckerToggleAsyncTest,
+       Different_SingleRead_StorageIsLonger) {
+  // Response body from the network.
+  const std::string body_from_net = "abc";
+
+  // Stored data for |kScriptURL|.
+  const std::vector<std::string> body_from_storage{"abc", "def"};
+
+  std::unique_ptr<network::TestURLLoaderFactory> loader_factory =
+      CreateLoaderFactoryWithRespone(GURL(kScriptURL), kSuccessHeader,
+                                     body_from_net, net::OK);
+
+  auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>();
+  auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>();
+  auto writer = std::make_unique<MockServiceWorkerResponseWriter>();
+  MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get();
+  compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage),
+                               IsAsync());
+
+  base::Optional<CheckResult> check_result;
+  std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker =
+      CreateSingleScriptUpdateCheckerWithoutHttpCache(
+          kScriptURL, GURL(kScope), std::move(compare_reader),
+          std::move(copy_reader), std::move(writer), loader_factory.get(),
+          &check_result);
+
+  if (IsAsync()) {
+    // Blocked on reading the header.
+    base::RunLoop().RunUntilIdle();
+    EXPECT_FALSE(check_result.has_value());
+
+    // Unblock the header, and then blocked on reading the body.
+    compare_reader_rawptr->CompletePendingRead();
+    base::RunLoop().RunUntilIdle();
+    EXPECT_FALSE(check_result.has_value());
+
+    // Unblock the body ("abc"). At this point, data from the network reaches
+    // the end.
+    compare_reader_rawptr->CompletePendingRead();
+  }
+
+  // Complete the comparison of the body. It should be different.
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(check_result.has_value());
+  EXPECT_EQ(check_result.value().result,
+            ServiceWorkerSingleScriptUpdateChecker::Result::kDifferent);
+  EXPECT_EQ(check_result.value().url, kScriptURL);
+
+  // The update checker realizes that the script is different before reaching
+  // the end of the script from the disk cache.
+  EXPECT_FALSE(compare_reader_rawptr->AllExpectedReadsDone());
+}
+
+TEST_P(ServiceWorkerSingleScriptUpdateCheckerToggleAsyncTest,
+       Different_SingleRead_DifferentBody) {
+  // Response body from the network.
+  const std::string body_from_net = "abc";
+
+  // Stored data for |kScriptURL|.
+  const std::vector<std::string> body_from_storage{"abx"};
+
+  std::unique_ptr<network::TestURLLoaderFactory> loader_factory =
+      CreateLoaderFactoryWithRespone(GURL(kScriptURL), kSuccessHeader,
+                                     body_from_net, net::OK);
+
+  auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>();
+  auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>();
+  auto writer = std::make_unique<MockServiceWorkerResponseWriter>();
+  MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get();
+  compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage),
+                               IsAsync());
+
+  base::Optional<CheckResult> check_result;
+  std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker =
+      CreateSingleScriptUpdateCheckerWithoutHttpCache(
+          kScriptURL, GURL(kScope), std::move(compare_reader),
+          std::move(copy_reader), std::move(writer), loader_factory.get(),
+          &check_result);
+
+  if (IsAsync()) {
+    // Blocked on reading the header.
+    base::RunLoop().RunUntilIdle();
+    EXPECT_FALSE(check_result.has_value());
+
+    // Unblock the header, and then blocked on reading the body.
+    compare_reader_rawptr->CompletePendingRead();
+    base::RunLoop().RunUntilIdle();
+    EXPECT_FALSE(check_result.has_value());
+
+    // Unblock the body ("abx").
+    compare_reader_rawptr->CompletePendingRead();
+  }
+
+  // Complete the comparison of the body. It should be different.
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(check_result.has_value());
+  EXPECT_EQ(check_result.value().result,
+            ServiceWorkerSingleScriptUpdateChecker::Result::kDifferent);
+  EXPECT_EQ(check_result.value().url, kScriptURL);
+  EXPECT_TRUE(compare_reader_rawptr->AllExpectedReadsDone());
+}
+
+TEST_P(ServiceWorkerSingleScriptUpdateCheckerToggleAsyncTest,
+       Different_MultipleRead_NetworkIsLonger) {
+  // Response body from the network.
+  const std::string body_from_net = "abcdef";
+
+  // Stored data for |kScriptURL| (the data for compare reader).
+  const std::vector<std::string> body_from_storage{"ab", "c", ""};
+
+  std::unique_ptr<network::TestURLLoaderFactory> loader_factory =
+      CreateLoaderFactoryWithRespone(GURL(kScriptURL), kSuccessHeader,
+                                     body_from_net, net::OK);
+
+  auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>();
+  auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>();
+  auto writer = std::make_unique<MockServiceWorkerResponseWriter>();
+  MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get();
+  compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage),
+                               IsAsync());
+
+  base::Optional<CheckResult> check_result;
+  std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker =
+      CreateSingleScriptUpdateCheckerWithoutHttpCache(
+          kScriptURL, GURL(kScope), std::move(compare_reader),
+          std::move(copy_reader), std::move(writer), loader_factory.get(),
+          &check_result);
+
+  if (IsAsync()) {
+    // Blocked on reading the header.
+    base::RunLoop().RunUntilIdle();
+    EXPECT_FALSE(check_result.has_value());
+
+    // Unblock the header, and then blocked on reading the body.
+    compare_reader_rawptr->CompletePendingRead();
+    base::RunLoop().RunUntilIdle();
+    EXPECT_FALSE(check_result.has_value());
+
+    // Unblock the body from storage ("ab"), and then blocked on reading the
+    // body again.
+    compare_reader_rawptr->CompletePendingRead();
+    base::RunLoop().RunUntilIdle();
+    EXPECT_FALSE(check_result.has_value());
+
+    // Unblock the body from storage ("c"), and then blocked on reading the body
+    // again.
+    compare_reader_rawptr->CompletePendingRead();
+    base::RunLoop().RunUntilIdle();
+    EXPECT_FALSE(check_result.has_value());
+
+    // Unblock the body from storage (""). The cache writer detects the end of
+    // the body from the disk cache.
+    compare_reader_rawptr->CompletePendingRead();
+  }
+
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(check_result.has_value());
+  EXPECT_EQ(check_result.value().result,
+            ServiceWorkerSingleScriptUpdateChecker::Result::kDifferent);
+  EXPECT_EQ(check_result.value().url, kScriptURL);
+  EXPECT_TRUE(compare_reader_rawptr->AllExpectedReadsDone());
+}
+
+TEST_P(ServiceWorkerSingleScriptUpdateCheckerToggleAsyncTest,
+       Different_MultipleRead_StorageIsLonger) {
+  // Response body from the network.
+  const std::string body_from_net = "abc";
+
+  // Stored data for |kScriptURL| (the data for compare reader).
+  const std::vector<std::string> body_from_storage{"ab", "c", "def"};
+
+  std::unique_ptr<network::TestURLLoaderFactory> loader_factory =
+      CreateLoaderFactoryWithRespone(GURL(kScriptURL), kSuccessHeader,
+                                     body_from_net, net::OK);
+
+  auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>();
+  auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>();
+  auto writer = std::make_unique<MockServiceWorkerResponseWriter>();
+  MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get();
+  compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage),
+                               IsAsync());
+
+  base::Optional<CheckResult> check_result;
+  std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker =
+      CreateSingleScriptUpdateCheckerWithoutHttpCache(
+          kScriptURL, GURL(kScope), std::move(compare_reader),
+          std::move(copy_reader), std::move(writer), loader_factory.get(),
+          &check_result);
+
+  if (IsAsync()) {
+    // Blocked on reading the header.
+    base::RunLoop().RunUntilIdle();
+    EXPECT_FALSE(check_result.has_value());
+
+    // Unblock the header, and then blocked on reading the body.
+    compare_reader_rawptr->CompletePendingRead();
+    base::RunLoop().RunUntilIdle();
+    EXPECT_FALSE(check_result.has_value());
+
+    // Unblock the body from storage ("ab"), and then blocked on reading the
+    // body again.
+    compare_reader_rawptr->CompletePendingRead();
+    base::RunLoop().RunUntilIdle();
+    EXPECT_FALSE(check_result.has_value());
+
+    // Unblock the body from storage ("c"). At this point, data from the network
+    // reaches the end.
+    compare_reader_rawptr->CompletePendingRead();
+  }
+
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(check_result.has_value());
+  EXPECT_EQ(check_result.value().result,
+            ServiceWorkerSingleScriptUpdateChecker::Result::kDifferent);
+  EXPECT_EQ(check_result.value().url, kScriptURL);
+
+  // The update checker realizes that the script is different before reaching
+  // the end of the script from the disk cache.
+  EXPECT_FALSE(compare_reader_rawptr->AllExpectedReadsDone());
+}
+
+TEST_P(ServiceWorkerSingleScriptUpdateCheckerToggleAsyncTest,
+       Different_MultipleRead_DifferentBody) {
+  // Response body from the network.
+  const std::string body_from_net = "abc";
+
+  // Stored data for |kScriptURL| (the data for compare reader).
+  const std::vector<std::string> body_from_storage{"ab", "x"};
+
+  std::unique_ptr<network::TestURLLoaderFactory> loader_factory =
+      CreateLoaderFactoryWithRespone(GURL(kScriptURL), kSuccessHeader,
+                                     body_from_net, net::OK);
+
+  auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>();
+  auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>();
+  auto writer = std::make_unique<MockServiceWorkerResponseWriter>();
+  MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get();
+  compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage),
+                               IsAsync());
+
+  base::Optional<CheckResult> check_result;
+  std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker =
+      CreateSingleScriptUpdateCheckerWithoutHttpCache(
+          kScriptURL, GURL(kScope), std::move(compare_reader),
+          std::move(copy_reader), std::move(writer), loader_factory.get(),
+          &check_result);
+
+  if (IsAsync()) {
+    // Blocked on reading the header.
+    base::RunLoop().RunUntilIdle();
+    EXPECT_FALSE(check_result.has_value());
+
+    // Unblock the header, and then blocked on reading the body.
+    compare_reader_rawptr->CompletePendingRead();
+    base::RunLoop().RunUntilIdle();
+    EXPECT_FALSE(check_result.has_value());
+
+    // Unblock the body from storage ("ab"), and then blocked on reading the
+    // body again.
+    compare_reader_rawptr->CompletePendingRead();
+    base::RunLoop().RunUntilIdle();
+    EXPECT_FALSE(check_result.has_value());
+
+    // Unblock the body from storage ("x"), which is different from the body
+    // from the network.
+    compare_reader_rawptr->CompletePendingRead();
+  }
+
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(check_result.has_value());
+  EXPECT_EQ(check_result.value().result,
+            ServiceWorkerSingleScriptUpdateChecker::Result::kDifferent);
+  EXPECT_EQ(check_result.value().url, kScriptURL);
+  EXPECT_TRUE(compare_reader_rawptr->AllExpectedReadsDone());
+}
+
+TEST_F(ServiceWorkerSingleScriptUpdateCheckerTest,
+       PendingReadWithErrorStatusShouldNotLeak) {
+  // Response body from the network.
+  const std::string body_from_net("abc");
+
+  // Stored data for |kScriptURL| (the data for compare reader).
+  const std::vector<std::string> body_from_storage{"ab", "c"};
+
+  auto loader_factory = std::make_unique<network::TestURLLoaderFactory>();
+  auto compare_reader = std::make_unique<MockServiceWorkerResponseReader>();
+  auto copy_reader = std::make_unique<MockServiceWorkerResponseReader>();
+  auto writer = std::make_unique<MockServiceWorkerResponseWriter>();
+  MockServiceWorkerResponseReader* compare_reader_rawptr = compare_reader.get();
+  compare_reader->ExpectReadOk(body_from_storage, TotalBytes(body_from_storage),
+                               /*async=*/true);
+
+  base::Optional<CheckResult> check_result;
+  std::unique_ptr<ServiceWorkerSingleScriptUpdateChecker> checker =
+      CreateSingleScriptUpdateCheckerWithoutHttpCache(
+          kScriptURL, GURL(kScope), std::move(compare_reader),
+          std::move(copy_reader), std::move(writer), loader_factory.get(),
+          &check_result);
+
+  // The update checker sends a request to the loader. The testing loader keeps
+  // the request.
+  base::RunLoop().RunUntilIdle();
+  network::TestURLLoaderFactory::PendingRequest* request =
+      loader_factory->GetPendingRequest(0);
+  ASSERT_TRUE(request);
+
+  // Simulate to send the head and the body back to the checker.
+  // Note that OnComplete() is not called yet.
+  {
+    network::ResourceResponseHead head =
+        network::CreateResourceResponseHead(net::HTTP_OK);
+    head.headers = base::MakeRefCounted<net::HttpResponseHeaders>(
+        net::HttpUtil::AssembleRawHeaders(kSuccessHeader));
+    head.headers->GetMimeType(&head.mime_type);
+    request->client->OnReceiveResponse(head);
+
+    MojoCreateDataPipeOptions options;
+    options.struct_size = sizeof(MojoCreateDataPipeOptions);
+    options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
+    options.element_num_bytes = 1;
+    options.capacity_num_bytes = body_from_net.size();
+    mojo::ScopedDataPipeConsumerHandle consumer;
+    mojo::ScopedDataPipeProducerHandle producer;
+    EXPECT_EQ(MOJO_RESULT_OK,
+              mojo::CreateDataPipe(&options, &producer, &consumer));
+    uint32_t bytes_written = body_from_net.size();
+    EXPECT_EQ(MOJO_RESULT_OK,
+              producer->WriteData(body_from_net.data(), &bytes_written,
+                                  MOJO_WRITE_DATA_FLAG_ALL_OR_NONE));
+    request->client->OnStartLoadingResponseBody(std::move(consumer));
+  }
+
+  // Blocked on reading the header from the storage due to the asynchronous
+  // read.
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(check_result.has_value());
+
+  // Update check stops in CompareReader() due to the asynchronous read of the
+  // |compare_reader|.
+  compare_reader_rawptr->CompletePendingRead();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(check_result.has_value());
+
+  // Return failed status code at this point. The update checker will throw the
+  // internal state away.
+  request->client->OnComplete(
+      network::URLLoaderCompletionStatus(net::ERR_ABORTED));
+  base::RunLoop().RunUntilIdle();
+
+  // Resume the pending read. This should not crash and return kFailed.
+  compare_reader_rawptr->CompletePendingRead();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(check_result.has_value());
+  EXPECT_EQ(ServiceWorkerSingleScriptUpdateChecker::Result::kFailed,
+            check_result.value().result);
+}
+
 // Tests cache validation behavior when updateViaCache is 'all'.
 TEST_F(ServiceWorkerSingleScriptUpdateCheckerTest, UpdateViaCache_All) {
   auto loader_factory = std::make_unique<network::TestURLLoaderFactory>();
diff --git a/content/browser/service_worker/service_worker_storage.cc b/content/browser/service_worker/service_worker_storage.cc
index 7c8be28c..8e58c7e 100644
--- a/content/browser/service_worker/service_worker_storage.cc
+++ b/content/browser/service_worker/service_worker_storage.cc
@@ -459,6 +459,7 @@
 
   uint64_t resources_total_size_bytes = 0;
   for (const auto& resource : resources) {
+    DCHECK_GE(resource.size_bytes, 0);
     resources_total_size_bytes += resource.size_bytes;
   }
   data.resources_total_size_bytes = resources_total_size_bytes;
diff --git a/content/browser/service_worker/service_worker_test_utils.cc b/content/browser/service_worker/service_worker_test_utils.cc
index 9ae72b78..00b258f 100644
--- a/content/browser/service_worker/service_worker_test_utils.cc
+++ b/content/browser/service_worker/service_worker_test_utils.cc
@@ -385,6 +385,7 @@
   DCHECK(!expected_reads_.empty());
   ExpectedRead expected = expected_reads_.front();
   EXPECT_FALSE(expected.info);
+  EXPECT_LE(static_cast<int>(expected.len), buf_len);
   if (expected.async) {
     pending_callback_ = std::move(callback);
     pending_buffer_ = buf;
diff --git a/content/browser/snapshot_browsertest.cc b/content/browser/snapshot_browsertest.cc
index 578e4c2a..1ed8faf 100644
--- a/content/browser/snapshot_browsertest.cc
+++ b/content/browser/snapshot_browsertest.cc
@@ -357,7 +357,7 @@
       ExpectedColor expected;
       do {
         PickRandomColor(&expected);
-      } while (base::ContainsValue(expected_snapshots, expected));
+      } while (base::Contains(expected_snapshots, expected));
       expected_snapshots.push_back(expected);
 
       std::string colorString = base::StringPrintf("#%02x%02x%02x", expected.r,
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 62b2fa86..7b97736 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -2496,7 +2496,7 @@
   RenderFrameHostImpl* frame = static_cast<RenderFrameHostImpl*>(rfh);
 
   if (is_fullscreen) {
-    if (!base::ContainsKey(fullscreen_frames_, frame)) {
+    if (!base::Contains(fullscreen_frames_, frame)) {
       fullscreen_frames_.insert(frame);
       FullscreenFrameSetUpdated();
     }
@@ -6913,7 +6913,7 @@
 base::Optional<gfx::Size> WebContentsImpl::GetFullscreenVideoSize() {
   base::Optional<MediaPlayerId> id =
       media_web_contents_observer_->GetFullscreenVideoMediaPlayerId();
-  if (id && base::ContainsKey(cached_video_sizes_, id.value()))
+  if (id && base::Contains(cached_video_sizes_, id.value()))
     return base::Optional<gfx::Size>(cached_video_sizes_[id.value()]);
   return base::nullopt;
 }
diff --git a/content/browser/web_package/signed_exchange_subresource_prefetch_browsertest.cc b/content/browser/web_package/signed_exchange_subresource_prefetch_browsertest.cc
index c16c8f9..53bfb94 100644
--- a/content/browser/web_package/signed_exchange_subresource_prefetch_browsertest.cc
+++ b/content/browser/web_package/signed_exchange_subresource_prefetch_browsertest.cc
@@ -217,10 +217,8 @@
       const std::string& inner_url_path,
       const net::SHA256HashValue& header_integrity,
       const std::string& content) {
-    int sxg_fetch_count = 0;
-    base::RunLoop sxg_prefetch_waiter;
-    RegisterRequestMonitor(embedded_test_server(), sxg_path, &sxg_fetch_count,
-                           &sxg_prefetch_waiter);
+    auto sxg_request_counter =
+        RequestCounter::CreateAndMonitor(embedded_test_server(), sxg_path);
     RegisterRequestHandler(embedded_test_server());
     ASSERT_TRUE(embedded_test_server()->Start());
 
@@ -241,15 +239,13 @@
         "text/html", {}, header_integrity)});
     ScopedSignedExchangeHandlerFactory scoped_factory(&factory);
 
-    EXPECT_EQ(0, prefetch_url_loader_called_);
+    EXPECT_EQ(0, sxg_request_counter->GetRequestCount());
 
     NavigateToURL(shell(), prefetch_page_url);
-    sxg_prefetch_waiter.Run();
-    EXPECT_EQ(1, sxg_fetch_count);
 
     WaitUntilLoaded(sxg_url);
 
-    EXPECT_EQ(1, prefetch_url_loader_called_);
+    EXPECT_EQ(1, sxg_request_counter->GetRequestCount());
   }
 
  private:
@@ -336,15 +332,10 @@
   const char* script_sxg_path = "/script_js.sxg";
   const char* script_inner_url_path = "/script.js";
 
-  int script_sxg_fetch_count = 0;
-  int script_fetch_count = 0;
-
-  base::RunLoop script_sxg_prefetch_waiter;
-  RegisterRequestMonitor(embedded_test_server(), script_sxg_path,
-                         &script_sxg_fetch_count, &script_sxg_prefetch_waiter);
-  base::RunLoop script_prefetch_waiter;
-  RegisterRequestMonitor(embedded_test_server(), script_inner_url_path,
-                         &script_fetch_count, &script_prefetch_waiter);
+  auto script_sxg_request_counter =
+      RequestCounter::CreateAndMonitor(embedded_test_server(), script_sxg_path);
+  auto script_request_counter = RequestCounter::CreateAndMonitor(
+      embedded_test_server(), script_inner_url_path);
   RegisterRequestHandler(embedded_test_server());
   ASSERT_TRUE(embedded_test_server()->Start());
 
@@ -396,20 +387,8 @@
            script_header_integrity)});
   ScopedSignedExchangeHandlerFactory scoped_factory(&factory);
 
-  EXPECT_EQ(0, prefetch_url_loader_called_);
+  EXPECT_EQ(0, GetPrefetchURLLoaderCallCount());
   NavigateToURL(shell(), prefetch_page_url);
-  if (base::FeatureList::IsEnabled(
-          features::kSignedExchangeSubresourcePrefetch)) {
-    script_sxg_prefetch_waiter.Run();
-    EXPECT_EQ(1, script_sxg_fetch_count);
-    EXPECT_EQ(0, script_fetch_count);
-    EXPECT_EQ(2, prefetch_url_loader_called_);
-  } else {
-    script_prefetch_waiter.Run();
-    EXPECT_EQ(0, script_sxg_fetch_count);
-    EXPECT_EQ(1, script_fetch_count);
-    EXPECT_EQ(1, prefetch_url_loader_called_);
-  }
 
   WaitUntilLoaded(sxg_page_url);
   if (base::FeatureList::IsEnabled(
@@ -419,6 +398,17 @@
     WaitUntilLoaded(inner_url_script_url);
   }
 
+  if (base::FeatureList::IsEnabled(
+          features::kSignedExchangeSubresourcePrefetch)) {
+    EXPECT_EQ(1, script_sxg_request_counter->GetRequestCount());
+    EXPECT_EQ(0, script_request_counter->GetRequestCount());
+    EXPECT_EQ(2, GetPrefetchURLLoaderCallCount());
+  } else {
+    EXPECT_EQ(0, script_sxg_request_counter->GetRequestCount());
+    EXPECT_EQ(1, script_request_counter->GetRequestCount());
+    EXPECT_EQ(1, GetPrefetchURLLoaderCallCount());
+  }
+
   const auto cached_exchanges = GetCachedExchanges(shell());
   if (base::FeatureList::IsEnabled(
           features::kSignedExchangeSubresourcePrefetch)) {
@@ -446,16 +436,16 @@
     // The content is loaded from PrefetchedSignedExchangeCache. And the script
     // is also loaded from PrefetchedSignedExchangeCache.
     NavigateToURLAndWaitTitle(sxg_page_url, "done");
-    EXPECT_EQ(1, script_sxg_fetch_count);
-    EXPECT_EQ(0, script_fetch_count);
+    EXPECT_EQ(1, script_sxg_request_counter->GetRequestCount());
+    EXPECT_EQ(0, script_request_counter->GetRequestCount());
   } else {
     // Subsequent navigation to the target URL wouldn't hit the network for
     // the target URL. The target content should still be read correctly.
     // The content is loaded from PrefetchedSignedExchangeCache. But the script
     // is loaded from the server.
     NavigateToURLAndWaitTitle(sxg_page_url, "from server");
-    EXPECT_EQ(0, script_sxg_fetch_count);
-    EXPECT_EQ(1, script_fetch_count);
+    EXPECT_EQ(0, script_sxg_request_counter->GetRequestCount());
+    EXPECT_EQ(1, script_request_counter->GetRequestCount());
   }
 }
 
@@ -512,22 +502,14 @@
       const std::string& script_inner_url_path,
       const std::string& expected_title,
       int expected_script_fetch_count) {
-    int script_sxg_fetch_count = 0;
-    int script_fetch_count = 0;
-
     // When |expected_script_fetch_count| is -1, we don't check the script fetch
     // count.
     const bool check_script_fetch_count = expected_script_fetch_count != -1;
 
-    base::RunLoop script_sxg_prefetch_waiter;
-    RegisterRequestMonitor(embedded_test_server(), script_sxg_path,
-                           &script_sxg_fetch_count,
-                           &script_sxg_prefetch_waiter);
-    base::RunLoop script_prefetch_waiter;
-    if (check_script_fetch_count) {
-      RegisterRequestMonitor(embedded_test_server(), script_inner_url_path,
-                             &script_fetch_count, &script_prefetch_waiter);
-    }
+    auto script_sxg_request_counter = RequestCounter::CreateAndMonitor(
+        embedded_test_server(), script_sxg_path);
+    auto script_request_counter = RequestCounter::CreateAndMonitor(
+        embedded_test_server(), script_inner_url_path);
     RegisterRequestHandler(embedded_test_server());
     ASSERT_TRUE(embedded_test_server()->Start());
 
@@ -581,18 +563,18 @@
              script_header_integrity)});
     ScopedSignedExchangeHandlerFactory scoped_factory(&factory);
 
-    EXPECT_EQ(0, prefetch_url_loader_called_);
+    EXPECT_EQ(0, GetPrefetchURLLoaderCallCount());
     NavigateToURL(shell(), prefetch_page_url);
-    script_sxg_prefetch_waiter.Run();
-    EXPECT_EQ(1, script_sxg_fetch_count);
-    if (check_script_fetch_count) {
-      EXPECT_EQ(0, script_fetch_count);
-    }
-    EXPECT_EQ(2, prefetch_url_loader_called_);
 
     WaitUntilLoaded(sxg_page_url);
     WaitUntilLoaded(sxg_script_url);
 
+    EXPECT_EQ(1, script_sxg_request_counter->GetRequestCount());
+    if (check_script_fetch_count) {
+      EXPECT_EQ(0, script_request_counter->GetRequestCount());
+    }
+    EXPECT_EQ(2, GetPrefetchURLLoaderCallCount());
+
     const auto cached_exchanges = GetCachedExchanges(shell());
     EXPECT_EQ(2u, cached_exchanges.size());
 
@@ -618,9 +600,10 @@
     //   The script is loaded from the server.
     NavigateToURLAndWaitTitle(sxg_page_url, expected_title);
 
-    EXPECT_EQ(1, script_sxg_fetch_count);
+    EXPECT_EQ(1, script_sxg_request_counter->GetRequestCount());
     if (check_script_fetch_count) {
-      EXPECT_EQ(expected_script_fetch_count, script_fetch_count);
+      EXPECT_EQ(expected_script_fetch_count,
+                script_request_counter->GetRequestCount());
     }
   }
 
@@ -646,21 +629,16 @@
       bool has_nosniff,
       const std::string& expected_title,
       network::CrossOriginReadBlocking::Action expected_action) {
-    int script_sxg_fetch_count = 0;
-    int script_fetch_count = 0;
     const char* prefetch_path = "/prefetch.html";
     const char* target_sxg_path = "/target.sxg";
     const char* target_path = "/target.html";
     const char* script_sxg_path = "/script_js.sxg";
     const char* script_path = "/script.js";
 
-    base::RunLoop script_sxg_prefetch_waiter;
-    RegisterRequestMonitor(embedded_test_server(), script_sxg_path,
-                           &script_sxg_fetch_count,
-                           &script_sxg_prefetch_waiter);
-    base::RunLoop script_prefetch_waiter;
-    RegisterRequestMonitor(embedded_test_server(), script_path,
-                           &script_fetch_count, &script_prefetch_waiter);
+    auto script_sxg_request_counter = RequestCounter::CreateAndMonitor(
+        embedded_test_server(), script_sxg_path);
+    auto script_request_counter =
+        RequestCounter::CreateAndMonitor(embedded_test_server(), script_path);
     RegisterRequestHandler(embedded_test_server());
     ASSERT_TRUE(embedded_test_server()->Start());
 
@@ -716,13 +694,13 @@
     ScopedSignedExchangeHandlerFactory scoped_factory(&factory);
 
     NavigateToURL(shell(), embedded_test_server()->GetURL(prefetch_path));
-    script_sxg_prefetch_waiter.Run();
-    EXPECT_EQ(1, script_sxg_fetch_count);
-    EXPECT_EQ(0, script_fetch_count);
 
     WaitUntilLoaded(target_sxg_url);
     WaitUntilLoaded(script_sxg_url);
 
+    EXPECT_EQ(1, script_sxg_request_counter->GetRequestCount());
+    EXPECT_EQ(0, script_request_counter->GetRequestCount());
+
     EXPECT_EQ(2u, GetCachedExchanges(shell()).size());
 
     {
@@ -735,8 +713,8 @@
                                    expected_action, 1);
     }
 
-    EXPECT_EQ(1, script_sxg_fetch_count);
-    EXPECT_EQ(0, script_fetch_count);
+    EXPECT_EQ(1, script_sxg_request_counter->GetRequestCount());
+    EXPECT_EQ(0, script_request_counter->GetRequestCount());
   }
 
  private:
@@ -814,10 +792,6 @@
 
 IN_PROC_BROWSER_TEST_P(SignedExchangeSubresourcePrefetchBrowserTest,
                        ImageSrcsetAndSizes) {
-  int image1_sxg_fetch_count = 0;
-  int image1_fetch_count = 0;
-  int image2_sxg_fetch_count = 0;
-  int image2_fetch_count = 0;
   const char* prefetch_path = "/prefetch.html";
   const char* target_sxg_path = "/target.sxg";
   const char* target_path = "/target.html";
@@ -826,18 +800,14 @@
   const char* image2_sxg_path = "/image2_png.sxg";
   const char* image2_path = "/image2.png";
 
-  base::RunLoop image1_sxg_prefetch_waiter;
-  RegisterRequestMonitor(embedded_test_server(), image1_sxg_path,
-                         &image1_sxg_fetch_count, &image1_sxg_prefetch_waiter);
-  base::RunLoop image1_prefetch_waiter;
-  RegisterRequestMonitor(embedded_test_server(), image1_path,
-                         &image1_fetch_count, &image1_prefetch_waiter);
-  base::RunLoop image2_sxg_prefetch_waiter;
-  RegisterRequestMonitor(embedded_test_server(), image2_sxg_path,
-                         &image2_sxg_fetch_count, &image2_sxg_prefetch_waiter);
-  base::RunLoop image2_prefetch_waiter;
-  RegisterRequestMonitor(embedded_test_server(), image2_path,
-                         &image2_fetch_count, &image2_prefetch_waiter);
+  auto image1_sxg_request_counter =
+      RequestCounter::CreateAndMonitor(embedded_test_server(), image1_sxg_path);
+  auto image2_sxg_request_counter =
+      RequestCounter::CreateAndMonitor(embedded_test_server(), image2_sxg_path);
+  auto image1_request_counter =
+      RequestCounter::CreateAndMonitor(embedded_test_server(), image1_path);
+  auto image2_request_counter =
+      RequestCounter::CreateAndMonitor(embedded_test_server(), image2_path);
   RegisterRequestHandler(embedded_test_server());
   ASSERT_TRUE(embedded_test_server()->Start());
 
@@ -913,15 +883,14 @@
   ScopedSignedExchangeHandlerFactory scoped_factory(&factory);
 
   NavigateToURL(shell(), embedded_test_server()->GetURL(prefetch_path));
-  image1_sxg_prefetch_waiter.Run();
-  EXPECT_EQ(1, image1_sxg_fetch_count);
-  EXPECT_EQ(0, image1_fetch_count);
-  EXPECT_EQ(0, image2_sxg_fetch_count);
-  EXPECT_EQ(0, image2_fetch_count);
-
   WaitUntilLoaded(target_sxg_url);
   WaitUntilLoaded(image1_sxg_url);
 
+  EXPECT_EQ(1, image1_sxg_request_counter->GetRequestCount());
+  EXPECT_EQ(0, image1_request_counter->GetRequestCount());
+  EXPECT_EQ(0, image2_sxg_request_counter->GetRequestCount());
+  EXPECT_EQ(0, image2_request_counter->GetRequestCount());
+
   const auto cached_exchanges = GetCachedExchanges(shell());
   EXPECT_EQ(2u, cached_exchanges.size());
 
@@ -939,18 +908,14 @@
 
   NavigateToURLAndWaitTitle(target_sxg_url, "done");
 
-  EXPECT_EQ(1, image1_sxg_fetch_count);
-  EXPECT_EQ(0, image1_fetch_count);
-  EXPECT_EQ(0, image2_sxg_fetch_count);
-  EXPECT_EQ(0, image2_fetch_count);
+  EXPECT_EQ(1, image1_sxg_request_counter->GetRequestCount());
+  EXPECT_EQ(0, image1_request_counter->GetRequestCount());
+  EXPECT_EQ(0, image2_sxg_request_counter->GetRequestCount());
+  EXPECT_EQ(0, image2_request_counter->GetRequestCount());
 }
 
 IN_PROC_BROWSER_TEST_P(SignedExchangeSubresourcePrefetchBrowserTest,
                        MultipleResources) {
-  int script1_sxg_fetch_count = 0;
-  int script1_fetch_count = 0;
-  int script2_sxg_fetch_count = 0;
-  int script2_fetch_count = 0;
   const char* prefetch_path = "/prefetch.html";
   const char* target_sxg_path = "/target.sxg";
   const char* target_path = "/target.html";
@@ -959,23 +924,17 @@
   const char* script2_sxg_path = "/script2_js.sxg";
   const char* script2_path = "/script2.js";
 
-  base::RunLoop script1_sxg_prefetch_waiter;
-  RegisterRequestMonitor(embedded_test_server(), script1_sxg_path,
-                         &script1_sxg_fetch_count,
-                         &script1_sxg_prefetch_waiter);
-  base::RunLoop script1_prefetch_waiter;
-  RegisterRequestMonitor(embedded_test_server(), script1_path,
-                         &script1_fetch_count, &script1_prefetch_waiter);
-  base::RunLoop script2_sxg_prefetch_waiter;
-  RegisterRequestMonitor(embedded_test_server(), script2_sxg_path,
-                         &script2_sxg_fetch_count,
-                         &script2_sxg_prefetch_waiter);
-  base::RunLoop script2_prefetch_waiter;
-  RegisterRequestMonitor(embedded_test_server(), script2_path,
-                         &script2_fetch_count, &script2_prefetch_waiter);
+  auto script1_sxg_request_counter = RequestCounter::CreateAndMonitor(
+      embedded_test_server(), script1_sxg_path);
+  auto script2_sxg_request_counter = RequestCounter::CreateAndMonitor(
+      embedded_test_server(), script2_sxg_path);
+  auto script1_request_counter =
+      RequestCounter::CreateAndMonitor(embedded_test_server(), script1_path);
+  auto script2_request_counter =
+      RequestCounter::CreateAndMonitor(embedded_test_server(), script2_path);
   RegisterRequestHandler(embedded_test_server());
   ASSERT_TRUE(embedded_test_server()->Start());
-  EXPECT_EQ(0, prefetch_url_loader_called_);
+  EXPECT_EQ(0, GetPrefetchURLLoaderCallCount());
 
   const GURL target_sxg_url = embedded_test_server()->GetURL(target_sxg_path);
   const GURL target_url = embedded_test_server()->GetURL(target_path);
@@ -1034,17 +993,16 @@
   ScopedSignedExchangeHandlerFactory scoped_factory(&factory);
 
   NavigateToURL(shell(), embedded_test_server()->GetURL(prefetch_path));
-  script1_sxg_prefetch_waiter.Run();
-  script2_sxg_prefetch_waiter.Run();
-  EXPECT_EQ(1, script1_sxg_fetch_count);
-  EXPECT_EQ(0, script1_fetch_count);
-  EXPECT_EQ(1, script2_sxg_fetch_count);
-  EXPECT_EQ(0, script2_fetch_count);
 
   WaitUntilLoaded(target_sxg_url);
   WaitUntilLoaded(script1_sxg_url);
   WaitUntilLoaded(script2_sxg_url);
 
+  EXPECT_EQ(1, script1_sxg_request_counter->GetRequestCount());
+  EXPECT_EQ(0, script1_request_counter->GetRequestCount());
+  EXPECT_EQ(1, script2_sxg_request_counter->GetRequestCount());
+  EXPECT_EQ(0, script2_request_counter->GetRequestCount());
+
   const auto cached_exchanges = GetCachedExchanges(shell());
   EXPECT_EQ(3u, cached_exchanges.size());
 
@@ -1072,31 +1030,27 @@
   // are also loaded from PrefetchedSignedExchangeCache.
   NavigateToURLAndWaitTitle(target_sxg_url, "done");
 
-  EXPECT_EQ(1, script1_sxg_fetch_count);
-  EXPECT_EQ(0, script1_fetch_count);
-  EXPECT_EQ(1, script2_sxg_fetch_count);
-  EXPECT_EQ(0, script2_fetch_count);
+  EXPECT_EQ(1, script1_sxg_request_counter->GetRequestCount());
+  EXPECT_EQ(0, script1_request_counter->GetRequestCount());
+  EXPECT_EQ(1, script2_sxg_request_counter->GetRequestCount());
+  EXPECT_EQ(0, script2_request_counter->GetRequestCount());
 }
 
 IN_PROC_BROWSER_TEST_P(SignedExchangeSubresourcePrefetchBrowserTest,
                        IntegrityMismatch) {
-  int script_sxg_fetch_count = 0;
-  int script_fetch_count = 0;
   const char* prefetch_path = "/prefetch.html";
   const char* target_sxg_path = "/target.sxg";
   const char* target_path = "/target.html";
   const char* script_path = "/script.js";
   const char* script_sxg_path = "/script_js.sxg";
 
-  base::RunLoop script_sxg_prefetch_waiter;
-  RegisterRequestMonitor(embedded_test_server(), script_sxg_path,
-                         &script_sxg_fetch_count, &script_sxg_prefetch_waiter);
-  base::RunLoop script_prefetch_waiter;
-  RegisterRequestMonitor(embedded_test_server(), script_path,
-                         &script_fetch_count, &script_prefetch_waiter);
+  auto script_sxg_request_counter =
+      RequestCounter::CreateAndMonitor(embedded_test_server(), script_sxg_path);
+  auto script_request_counter =
+      RequestCounter::CreateAndMonitor(embedded_test_server(), script_path);
   RegisterRequestHandler(embedded_test_server());
   ASSERT_TRUE(embedded_test_server()->Start());
-  EXPECT_EQ(0, prefetch_url_loader_called_);
+  EXPECT_EQ(0, GetPrefetchURLLoaderCallCount());
 
   const GURL target_sxg_url = embedded_test_server()->GetURL(target_sxg_path);
   const GURL target_url = embedded_test_server()->GetURL(target_path);
@@ -1142,27 +1096,24 @@
   ScopedSignedExchangeHandlerFactory scoped_factory(&factory);
 
   NavigateToURL(shell(), embedded_test_server()->GetURL(prefetch_path));
-  script_sxg_prefetch_waiter.Run();
-  EXPECT_EQ(1, script_sxg_fetch_count);
-  EXPECT_EQ(0, script_fetch_count);
 
   WaitUntilLoaded(target_sxg_url);
   WaitUntilLoaded(script_sxg_url);
 
+  EXPECT_EQ(1, script_sxg_request_counter->GetRequestCount());
+  EXPECT_EQ(0, script_request_counter->GetRequestCount());
+
   // The value of "header-integrity" in "allowed-alt-sxg" link header of the
   // inner response doesn't match the actual header integrity of script_js.sxg.
   // So the script request must go to the server.
   NavigateToURLAndWaitTitle(target_sxg_url, "from server");
 
-  EXPECT_EQ(1, script_fetch_count);
+  EXPECT_EQ(1, script_sxg_request_counter->GetRequestCount());
+  EXPECT_EQ(1, script_request_counter->GetRequestCount());
 }
 
 IN_PROC_BROWSER_TEST_P(SignedExchangeSubresourcePrefetchBrowserTest,
                        MultipleResources_IntegrityMismatch) {
-  int script1_sxg_fetch_count = 0;
-  int script1_fetch_count = 0;
-  int script2_sxg_fetch_count = 0;
-  int script2_fetch_count = 0;
   const char* prefetch_path = "/prefetch.html";
   const char* target_sxg_path = "/target.sxg";
   const char* target_path = "/target.html";
@@ -1171,23 +1122,17 @@
   const char* script2_sxg_path = "/script2_js.sxg";
   const char* script2_path = "/script2.js";
 
-  base::RunLoop script1_sxg_prefetch_waiter;
-  RegisterRequestMonitor(embedded_test_server(), script1_sxg_path,
-                         &script1_sxg_fetch_count,
-                         &script1_sxg_prefetch_waiter);
-  base::RunLoop script1_prefetch_waiter;
-  RegisterRequestMonitor(embedded_test_server(), script1_path,
-                         &script1_fetch_count, &script1_prefetch_waiter);
-  base::RunLoop script2_sxg_prefetch_waiter;
-  RegisterRequestMonitor(embedded_test_server(), script2_sxg_path,
-                         &script2_sxg_fetch_count,
-                         &script2_sxg_prefetch_waiter);
-  base::RunLoop script2_prefetch_waiter;
-  RegisterRequestMonitor(embedded_test_server(), script2_path,
-                         &script2_fetch_count, &script2_prefetch_waiter);
+  auto script1_sxg_request_counter = RequestCounter::CreateAndMonitor(
+      embedded_test_server(), script1_sxg_path);
+  auto script2_sxg_request_counter = RequestCounter::CreateAndMonitor(
+      embedded_test_server(), script2_sxg_path);
+  auto script1_request_counter =
+      RequestCounter::CreateAndMonitor(embedded_test_server(), script1_path);
+  auto script2_request_counter =
+      RequestCounter::CreateAndMonitor(embedded_test_server(), script2_path);
   RegisterRequestHandler(embedded_test_server());
   ASSERT_TRUE(embedded_test_server()->Start());
-  EXPECT_EQ(0, prefetch_url_loader_called_);
+  EXPECT_EQ(0, GetPrefetchURLLoaderCallCount());
 
   const GURL target_sxg_url = embedded_test_server()->GetURL(target_sxg_path);
   const GURL target_url = embedded_test_server()->GetURL(target_path);
@@ -1251,17 +1196,16 @@
   ScopedSignedExchangeHandlerFactory scoped_factory(&factory);
 
   NavigateToURL(shell(), embedded_test_server()->GetURL(prefetch_path));
-  script1_sxg_prefetch_waiter.Run();
-  script2_sxg_prefetch_waiter.Run();
-  EXPECT_EQ(1, script1_sxg_fetch_count);
-  EXPECT_EQ(0, script1_fetch_count);
-  EXPECT_EQ(1, script2_sxg_fetch_count);
-  EXPECT_EQ(0, script2_fetch_count);
 
   WaitUntilLoaded(target_sxg_url);
   WaitUntilLoaded(script1_sxg_url);
   WaitUntilLoaded(script2_sxg_url);
 
+  EXPECT_EQ(1, script1_sxg_request_counter->GetRequestCount());
+  EXPECT_EQ(0, script1_request_counter->GetRequestCount());
+  EXPECT_EQ(1, script2_sxg_request_counter->GetRequestCount());
+  EXPECT_EQ(0, script2_request_counter->GetRequestCount());
+
   const auto cached_exchanges = GetCachedExchanges(shell());
   EXPECT_EQ(3u, cached_exchanges.size());
 
@@ -1270,10 +1214,10 @@
   // So the all script requests must go to the server.
   NavigateToURLAndWaitTitle(target_sxg_url, "from server");
 
-  EXPECT_EQ(1, script1_sxg_fetch_count);
-  EXPECT_EQ(1, script1_fetch_count);
-  EXPECT_EQ(1, script2_sxg_fetch_count);
-  EXPECT_EQ(1, script2_fetch_count);
+  EXPECT_EQ(1, script1_sxg_request_counter->GetRequestCount());
+  EXPECT_EQ(1, script1_request_counter->GetRequestCount());
+  EXPECT_EQ(1, script2_sxg_request_counter->GetRequestCount());
+  EXPECT_EQ(1, script2_request_counter->GetRequestCount());
 }
 
 IN_PROC_BROWSER_TEST_P(SignedExchangeSubresourcePrefetchBrowserTest, CORS) {
diff --git a/content/common/content_security_policy/content_security_policy_unittest.cc b/content/common/content_security_policy/content_security_policy_unittest.cc
index 909de5c3..9fa20b12 100644
--- a/content/common/content_security_policy/content_security_policy_unittest.cc
+++ b/content/common/content_security_policy/content_security_policy_unittest.cc
@@ -22,7 +22,7 @@
   }
 
   bool SchemeShouldBypassCSP(const base::StringPiece& scheme) override {
-    return base::ContainsValue(scheme_to_bypass_, scheme);
+    return base::Contains(scheme_to_bypass_, scheme);
   }
 
  private:
diff --git a/content/common/font_cache_dispatcher_win.cc b/content/common/font_cache_dispatcher_win.cc
index 7b21544..d4cb522 100644
--- a/content/common/font_cache_dispatcher_win.cc
+++ b/content/common/font_cache_dispatcher_win.cc
@@ -45,7 +45,7 @@
 
     base::string16 font_name = font.lfFaceName;
     int ref_count_inc = 1;
-    if (!base::ContainsValue(dispatcher_font_map_[dispatcher], font_name)) {
+    if (!base::Contains(dispatcher_font_map_[dispatcher], font_name)) {
       // Requested font is new to cache.
       dispatcher_font_map_[dispatcher].push_back(font_name);
     } else {
diff --git a/content/common/origin_util.cc b/content/common/origin_util.cc
index 1261b8e..d6cd3855 100644
--- a/content/common/origin_util.cc
+++ b/content/common/origin_util.cc
@@ -33,7 +33,7 @@
   if (url.SchemeIsHTTPOrHTTPS() && IsOriginSecure(url))
     return true;
 
-  if (base::ContainsValue(GetServiceWorkerSchemes(), url.scheme())) {
+  if (base::Contains(GetServiceWorkerSchemes(), url.scheme())) {
     return true;
   }
 
diff --git a/content/common/throttling_url_loader.cc b/content/common/throttling_url_loader.cc
index a2c6e14..ecdedf3 100644
--- a/content/common/throttling_url_loader.cc
+++ b/content/common/throttling_url_loader.cc
@@ -21,7 +21,7 @@
 void MergeRemovedHeaders(std::vector<std::string>* removed_headers_A,
                          const std::vector<std::string>& removed_headers_B) {
   for (auto& header : removed_headers_B) {
-    if (!base::ContainsValue(*removed_headers_A, header))
+    if (!base::Contains(*removed_headers_A, header))
       removed_headers_A->emplace_back(std::move(header));
   }
 }
diff --git a/content/public/browser/child_process_security_policy.h b/content/public/browser/child_process_security_policy.h
index 59608e1..e761298 100644
--- a/content/public/browser/child_process_security_policy.h
+++ b/content/public/browser/child_process_security_policy.h
@@ -19,6 +19,7 @@
 namespace content {
 
 class BrowserContext;
+class IsolatedOriginPattern;
 
 // The ChildProcessSecurityPolicy class is used to grant and revoke security
 // capabilities for child processes.  For example, it restricts whether a child
@@ -264,7 +265,15 @@
   // attempts to re-add an origin for the same |browser_context| will be
   // ignored.
   virtual void AddIsolatedOrigins(
-      std::vector<url::Origin> origins,
+      const std::vector<url::Origin>& origins,
+      BrowserContext* browser_context = nullptr) = 0;
+
+  // Semantically identical to the above, but accepts IsolatedOriginPatterns, a
+  // container class which holds wildcard patterns for isolated origins, such as
+  // https://**.foo.com indicating that all domains that match the pattern are
+  // to be given their own isolated process.
+  virtual void AddIsolatedOrigins(
+      const std::vector<IsolatedOriginPattern>& patterns,
       BrowserContext* browser_context = nullptr) = 0;
 
   // Returns true if |origin| is a globally (not per-profile) isolated origin.
diff --git a/content/public/browser/notification_registrar.cc b/content/public/browser/notification_registrar.cc
index 5fed6a2..91f205d 100644
--- a/content/public/browser/notification_registrar.cc
+++ b/content/public/browser/notification_registrar.cc
@@ -110,7 +110,7 @@
                                          const NotificationSource& source) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   Record record = { observer, type, source };
-  return base::ContainsValue(registered_, record);
+  return base::Contains(registered_, record);
 }
 
 }  // namespace content
diff --git a/content/public/common/referrer.cc b/content/public/common/referrer.cc
index de78496..04706c4c 100644
--- a/content/public/common/referrer.cc
+++ b/content/public/common/referrer.cc
@@ -6,8 +6,9 @@
 
 #include <string>
 
-#include "base/command_line.h"
+#include "base/numerics/safe_conversions.h"
 #include "content/public/common/content_features.h"
+#include "net/base/features.h"
 #include "services/network/loader_util.h"
 
 namespace content {
@@ -81,6 +82,13 @@
       }
       break;
   }
+
+  if (base::FeatureList::IsEnabled(net::features::kCapRefererHeaderLength) &&
+      base::saturated_cast<int>(sanitized_referrer.url.spec().length()) >
+          net::features::kMaxRefererHeaderLength.Get()) {
+    sanitized_referrer.url = sanitized_referrer.url.GetOrigin();
+  }
+
   return sanitized_referrer;
 }
 
diff --git a/content/public/test/browser_test_base.cc b/content/public/test/browser_test_base.cc
index 9c9e8c96..bf2ed9a 100644
--- a/content/public/test/browser_test_base.cc
+++ b/content/public/test/browser_test_base.cc
@@ -410,7 +410,6 @@
   // for the test harness to be able to delete temp dirs.
   base::ThreadRestrictions::SetIOAllowed(true);
 
-  BrowserTaskExecutor::ResetForTesting();
 #else
   GetContentMainParams()->ui_task = ui_task.release();
   GetContentMainParams()->created_main_parts_closure =
diff --git a/content/public/test/browser_test_utils.cc b/content/public/test/browser_test_utils.cc
index 2d34e18..cde062e 100644
--- a/content/public/test/browser_test_utils.cc
+++ b/content/public/test/browser_test_utils.cc
@@ -2156,7 +2156,7 @@
 
 void TitleWatcher::TestTitle() {
   const base::string16& current_title = web_contents()->GetTitle();
-  if (base::ContainsValue(expected_titles_, current_title)) {
+  if (base::Contains(expected_titles_, current_title)) {
     observed_title_ = current_title;
     run_loop_.Quit();
   }
diff --git a/content/public/test/download_test_observer.cc b/content/public/test/download_test_observer.cc
index 59472dec..6194485 100644
--- a/content/public/test/download_test_observer.cc
+++ b/content/public/test/download_test_observer.cc
@@ -140,7 +140,7 @@
 void DownloadTestObserver::OnDownloadUpdated(download::DownloadItem* download) {
   // Real UI code gets the user's response after returning from the observer.
   if (download->IsDangerous() &&
-      !base::ContainsKey(dangerous_downloads_seen_, download->GetId())) {
+      !base::Contains(dangerous_downloads_seen_, download->GetId())) {
     dangerous_downloads_seen_.insert(download->GetId());
 
     // Calling ValidateDangerousDownload() at this point will
diff --git a/content/public/test/test_launcher.cc b/content/public/test/test_launcher.cc
index cf8db90..b9d52aab 100644
--- a/content/public/test/test_launcher.cc
+++ b/content/public/test/test_launcher.cc
@@ -263,7 +263,7 @@
     // Stack all dependent tests and run them sequentially.
     test_list.push_back(test_name);
     if (!IsPreTestName(test_name)) {
-      if (!base::ContainsKey(user_data_dir_map_, test_name)) {
+      if (!base::Contains(user_data_dir_map_, test_name)) {
         base::FilePath temp_dir;
         CHECK(base::CreateTemporaryDirInDir(temp_dir_.GetPath(),
                                             FILE_PATH_LITERAL("d"), &temp_dir));
@@ -293,7 +293,7 @@
     // Make sure PRE_ tests and tests that depend on them share the same
     // data directory - based it on the test name without prefixes.
     std::string test_name_no_pre(RemoveAnyPrePrefixes(full_name));
-    if (!base::ContainsKey(user_data_dir_map_, test_name_no_pre)) {
+    if (!base::Contains(user_data_dir_map_, test_name_no_pre)) {
       base::FilePath temp_dir;
       CHECK(base::CreateTemporaryDirInDir(temp_dir_.GetPath(),
                                           FILE_PATH_LITERAL("d"), &temp_dir));
@@ -429,7 +429,7 @@
 
     // No other tests depend on this, we can delete the temporary directory now.
     // Do so to avoid too many temporary files using lots of disk space.
-  if (base::ContainsKey(user_data_dir_map_, test_name)) {
+  if (base::Contains(user_data_dir_map_, test_name)) {
     if (!base::DeleteFile(user_data_dir_map_[test_name], true)) {
       LOG(WARNING) << "Failed to delete "
                    << user_data_dir_map_[test_name].value();
diff --git a/content/renderer/BUILD.gn b/content/renderer/BUILD.gn
index b281d1b..bde316ef 100644
--- a/content/renderer/BUILD.gn
+++ b/content/renderer/BUILD.gn
@@ -240,8 +240,6 @@
     "media/stream/user_media_client_impl.h",
     "media/stream/user_media_processor.cc",
     "media/stream/user_media_processor.h",
-    "media/stream/webaudio_media_stream_source.cc",
-    "media/stream/webaudio_media_stream_source.h",
     "media/stream/webmediaplayer_ms.cc",
     "media/stream/webmediaplayer_ms.h",
     "media/stream/webmediaplayer_ms_compositor.cc",
diff --git a/content/renderer/accessibility/ax_image_annotator.cc b/content/renderer/accessibility/ax_image_annotator.cc
index 8021718..7c40469 100644
--- a/content/renderer/accessibility/ax_image_annotator.cc
+++ b/content/renderer/accessibility/ax_image_annotator.cc
@@ -70,12 +70,12 @@
 
 bool AXImageAnnotator::HasImageInCache(const blink::WebAXObject& image) const {
   DCHECK(!image.IsDetached());
-  return base::ContainsKey(image_annotations_, image.AxID());
+  return base::Contains(image_annotations_, image.AxID());
 }
 
 void AXImageAnnotator::OnImageAdded(blink::WebAXObject& image) {
   DCHECK(!image.IsDetached());
-  DCHECK(!base::ContainsKey(image_annotations_, image.AxID()));
+  DCHECK(!base::Contains(image_annotations_, image.AxID()));
   const std::string image_id = GenerateImageSourceId(image);
   if (image_id.empty())
     return;
@@ -93,7 +93,7 @@
 
 void AXImageAnnotator::OnImageUpdated(blink::WebAXObject& image) {
   DCHECK(!image.IsDetached());
-  DCHECK(base::ContainsKey(image_annotations_, image.AxID()));
+  DCHECK(base::Contains(image_annotations_, image.AxID()));
   const std::string image_id = GenerateImageSourceId(image);
   if (image_id.empty())
     return;
@@ -242,7 +242,7 @@
 void AXImageAnnotator::OnImageAnnotated(
     const blink::WebAXObject& image,
     image_annotation::mojom::AnnotateImageResultPtr result) {
-  if (!base::ContainsKey(image_annotations_, image.AxID()))
+  if (!base::Contains(image_annotations_, image.AxID()))
     return;
 
   if (image.IsDetached()) {
diff --git a/content/renderer/accessibility/blink_ax_tree_source.cc b/content/renderer/accessibility/blink_ax_tree_source.cc
index 6c4d356..9df33a8 100644
--- a/content/renderer/accessibility/blink_ax_tree_source.cc
+++ b/content/renderer/accessibility/blink_ax_tree_source.cc
@@ -344,7 +344,7 @@
   WebAXObject ancestor = obj;
   while (!ancestor.IsDetached()) {
     int32_t ancestor_id = ancestor.AxID();
-    if (base::ContainsKey(load_inline_text_boxes_ids_, ancestor_id) ||
+    if (base::Contains(load_inline_text_boxes_ids_, ancestor_id) ||
         (ancestor_id == focus_id && ancestor.IsEditable())) {
       return true;
     }
diff --git a/content/renderer/android/synchronous_layer_tree_frame_sink.cc b/content/renderer/android/synchronous_layer_tree_frame_sink.cc
index 3bfc4880..20d62c2 100644
--- a/content/renderer/android/synchronous_layer_tree_frame_sink.cc
+++ b/content/renderer/android/synchronous_layer_tree_frame_sink.cc
@@ -358,7 +358,7 @@
 }
 
 void SynchronousLayerTreeFrameSink::DidAllocateSharedBitmap(
-    mojo::ScopedSharedBufferHandle buffer,
+    base::ReadOnlySharedMemoryRegion region,
     const viz::SharedBitmapId& id) {
   // Webview does not use software compositing (other than resourceless draws,
   // but this is called for software /resources/).
diff --git a/content/renderer/android/synchronous_layer_tree_frame_sink.h b/content/renderer/android/synchronous_layer_tree_frame_sink.h
index a1359160..a41f849 100644
--- a/content/renderer/android/synchronous_layer_tree_frame_sink.h
+++ b/content/renderer/android/synchronous_layer_tree_frame_sink.h
@@ -13,6 +13,7 @@
 #include "base/cancelable_callback.h"
 #include "base/compiler_specific.h"
 #include "base/macros.h"
+#include "base/memory/read_only_shared_memory_region.h"
 #include "base/memory/ref_counted.h"
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_checker.h"
@@ -98,7 +99,7 @@
                              bool hit_test_data_changed,
                              bool show_hit_test_borders) override;
   void DidNotProduceFrame(const viz::BeginFrameAck& ack) override;
-  void DidAllocateSharedBitmap(mojo::ScopedSharedBufferHandle buffer,
+  void DidAllocateSharedBitmap(base::ReadOnlySharedMemoryRegion region,
                                const viz::SharedBitmapId& id) override;
   void DidDeleteSharedBitmap(const viz::SharedBitmapId& id) override;
   void Invalidate(bool needs_draw) override;
diff --git a/content/renderer/categorized_worker_pool.cc b/content/renderer/categorized_worker_pool.cc
index c13116f..c5bf882 100644
--- a/content/renderer/categorized_worker_pool.cc
+++ b/content/renderer/categorized_worker_pool.cc
@@ -236,7 +236,7 @@
   CollectCompletedTasksWithLockAcquired(namespace_token_, &completed_tasks_);
 
   base::EraseIf(tasks_, [this](const scoped_refptr<cc::Task>& e) {
-    return base::ContainsValue(this->completed_tasks_, e);
+    return base::Contains(this->completed_tasks_, e);
   });
 
   tasks_.push_back(base::MakeRefCounted<ClosureTask>(std::move(task)));
diff --git a/content/renderer/media/stream/media_stream_center.cc b/content/renderer/media/stream/media_stream_center.cc
index 47b1d41..089451e 100644
--- a/content/renderer/media/stream/media_stream_center.cc
+++ b/content/renderer/media/stream/media_stream_center.cc
@@ -14,12 +14,12 @@
 #include "content/public/common/content_switches.h"
 #include "content/public/renderer/render_thread.h"
 #include "content/renderer/media/stream/processed_local_audio_source.h"
-#include "content/renderer/media/stream/webaudio_media_stream_source.h"
 #include "content/renderer/media/webrtc_local_audio_source_provider.h"
 #include "media/base/sample_format.h"
 #include "third_party/blink/public/platform/modules/mediastream/media_stream_audio_track.h"
 #include "third_party/blink/public/platform/modules/mediastream/web_media_stream_audio_sink.h"
 #include "third_party/blink/public/platform/modules/mediastream/web_platform_media_stream_source.h"
+#include "third_party/blink/public/platform/modules/mediastream/webaudio_media_stream_source.h"
 #include "third_party/blink/public/platform/web_media_constraints.h"
 #include "third_party/blink/public/platform/web_media_stream.h"
 #include "third_party/blink/public/platform/web_media_stream_source.h"
@@ -52,7 +52,8 @@
   // special case code isn't needed here.
   if (!media_stream_source && source.RequiresAudioConsumer()) {
     DVLOG(1) << "Creating WebAudio media stream source.";
-    media_stream_source = new WebAudioMediaStreamSource(&source, task_runner);
+    media_stream_source =
+        new blink::WebAudioMediaStreamSource(&source, task_runner);
     source.SetPlatformSource(
         base::WrapUnique(media_stream_source));  // Takes ownership.
 
diff --git a/content/renderer/media/stream/media_stream_constraints_util_audio_unittest.cc b/content/renderer/media/stream/media_stream_constraints_util_audio_unittest.cc
index fb82b60..f763d0a 100644
--- a/content/renderer/media/stream/media_stream_constraints_util_audio_unittest.cc
+++ b/content/renderer/media/stream/media_stream_constraints_util_audio_unittest.cc
@@ -63,7 +63,7 @@
 
 template <typename T>
 static bool Contains(const std::vector<T>& vector, T value) {
-  return base::ContainsValue(vector, value);
+  return base::Contains(vector, value);
 }
 
 }  // namespace
diff --git a/content/renderer/media/stream/user_media_processor.cc b/content/renderer/media/stream/user_media_processor.cc
index ff20048..38128c4 100644
--- a/content/renderer/media/stream/user_media_processor.cc
+++ b/content/renderer/media/stream/user_media_processor.cc
@@ -440,7 +440,7 @@
     const blink::WebString& result_name) {
   // Check if we're waiting to be notified of this source.  If not, then we'll
   // ignore the notification.
-  if (base::ContainsValue(sources_waiting_for_callback_, source))
+  if (base::Contains(sources_waiting_for_callback_, source))
     OnTrackStarted(source, result, result_name);
 }
 
diff --git a/content/renderer/media/webrtc/mock_peer_connection_impl.cc b/content/renderer/media/webrtc/mock_peer_connection_impl.cc
index 353044a..d2882c8 100644
--- a/content/renderer/media/webrtc/mock_peer_connection_impl.cc
+++ b/content/renderer/media/webrtc/mock_peer_connection_impl.cc
@@ -351,7 +351,7 @@
       return webrtc::RTCError(webrtc::RTCErrorType::INVALID_PARAMETER);
   }
   for (const auto& stream_id : stream_ids) {
-    if (!base::ContainsValue(local_stream_ids_, stream_id)) {
+    if (!base::Contains(local_stream_ids_, stream_id)) {
       stream_label_ = stream_id;
       local_stream_ids_.push_back(stream_id);
     }
diff --git a/content/renderer/media/webrtc/rtc_video_decoder_adapter.cc b/content/renderer/media/webrtc/rtc_video_decoder_adapter.cc
index 23be5bf..5f35909 100644
--- a/content/renderer/media/webrtc/rtc_video_decoder_adapter.cc
+++ b/content/renderer/media/webrtc/rtc_video_decoder_adapter.cc
@@ -440,7 +440,7 @@
 
   base::AutoLock auto_lock(lock_);
 
-  if (!base::ContainsValue(decode_timestamps_, timestamp)) {
+  if (!base::Contains(decode_timestamps_, timestamp)) {
     DVLOG(2) << "Discarding frame with timestamp " << timestamp;
     return;
   }
diff --git a/content/renderer/media/webrtc/transmission_encoding_info_handler.cc b/content/renderer/media/webrtc/transmission_encoding_info_handler.cc
index 76c29a8..21fe0dd4 100644
--- a/content/renderer/media/webrtc/transmission_encoding_info_handler.cc
+++ b/content/renderer/media/webrtc/transmission_encoding_info_handler.cc
@@ -129,7 +129,7 @@
   } else if (base::StartsWith(mime_type, audio_prefix,
                               base::CompareCase::SENSITIVE)) {
     const std::string codec_name = mime_type.substr(strlen(audio_prefix));
-    if (base::ContainsKey(supported_audio_codecs_, codec_name))
+    if (base::Contains(supported_audio_codecs_, codec_name))
       return codec_name;
   }
   return "";
@@ -167,7 +167,7 @@
     info->supported = !codec_name.empty();
     if (info->supported) {
       const bool is_hardware_accelerated =
-          base::ContainsKey(hardware_accelerated_video_codecs_, codec_name);
+          base::Contains(hardware_accelerated_video_codecs_, codec_name);
       info->smooth =
           is_hardware_accelerated || CanCpuEncodeSmoothly(video_config);
       info->power_efficient = is_hardware_accelerated;
diff --git a/content/renderer/media/webrtc/webrtc_audio_device_impl.cc b/content/renderer/media/webrtc/webrtc_audio_device_impl.cc
index 82f7916..9363e2d 100644
--- a/content/renderer/media/webrtc/webrtc_audio_device_impl.cc
+++ b/content/renderer/media/webrtc/webrtc_audio_device_impl.cc
@@ -379,7 +379,7 @@
   DCHECK(!capturer->device().id.empty());
 
   base::AutoLock auto_lock(lock_);
-  DCHECK(!base::ContainsValue(capturers_, capturer));
+  DCHECK(!base::Contains(capturers_, capturer));
   capturers_.push_back(capturer);
   capturer->SetOutputDeviceForAec(output_device_id_for_aec_);
 }
@@ -398,7 +398,7 @@
   DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
   DCHECK(sink);
   base::AutoLock auto_lock(lock_);
-  DCHECK(!base::ContainsValue(playout_sinks_, sink));
+  DCHECK(!base::Contains(playout_sinks_, sink));
   playout_sinks_.push_back(sink);
 }
 
diff --git a/content/renderer/media/webrtc/webrtc_audio_renderer.cc b/content/renderer/media/webrtc/webrtc_audio_renderer.cc
index 32a1255..fbc1090 100644
--- a/content/renderer/media/webrtc/webrtc_audio_renderer.cc
+++ b/content/renderer/media/webrtc/webrtc_audio_renderer.cc
@@ -546,7 +546,7 @@
   DCHECK(state->playing());
   // Look up or add the |source| to the map.
   PlayingStates& array = source_playing_states_[source];
-  if (base::ContainsValue(array, state))
+  if (base::Contains(array, state))
     return false;
 
   array.push_back(state);
diff --git a/content/renderer/media/webrtc/webrtc_audio_sink.cc b/content/renderer/media/webrtc/webrtc_audio_sink.cc
index 8924c75..2a4e5d2 100644
--- a/content/renderer/media/webrtc/webrtc_audio_sink.cc
+++ b/content/renderer/media/webrtc/webrtc_audio_sink.cc
@@ -152,7 +152,7 @@
          signaling_task_runner_->RunsTasksInCurrentSequence());
   DCHECK(sink);
   base::AutoLock auto_lock(lock_);
-  DCHECK(!base::ContainsValue(sinks_, sink));
+  DCHECK(!base::Contains(sinks_, sink));
   sinks_.push_back(sink);
 }
 
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 7b982df..21bf76f 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -4785,7 +4785,6 @@
   UpdateEncoding(frame_, frame_->View()->PageEncoding().Utf8());
 
   // Reset warning state that prevents log spam.
-  certificate_warning_origins_.clear();
   tls_version_warning_origins_.clear();
 }
 
@@ -5434,7 +5433,7 @@
 void RenderFrameImpl::ReportLegacyTLSVersion(const blink::WebURL& url) {
   url::Origin origin = url::Origin::Create(GURL(url));
   // To prevent log spam, only log the message once per origin.
-  if (base::ContainsKey(tls_version_warning_origins_, origin))
+  if (base::Contains(tls_version_warning_origins_, origin))
     return;
 
   size_t num_warnings = tls_version_warning_origins_.size();
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index c3c439b..32b80ee3 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -1730,10 +1730,6 @@
   blink::mojom::ClipboardHostPtr clipboard_host_;
 #endif
 
-  // The origins for which a legacy certificate warning has been printed. The
-  // size of this set is capped, after which no more warnings are printed.
-  std::set<url::Origin> certificate_warning_origins_;
-
   // The origins for which a legacy TLS version warning has been printed. The
   // size of this set is capped, after which no more warnings are printed.
   std::set<url::Origin> tls_version_warning_origins_;
diff --git a/content/renderer/worker/worker_thread_registry.cc b/content/renderer/worker/worker_thread_registry.cc
index 97dae12..b77257a2 100644
--- a/content/renderer/worker/worker_thread_registry.cc
+++ b/content/renderer/worker/worker_thread_registry.cc
@@ -122,7 +122,7 @@
 
 base::TaskRunner* WorkerThreadRegistry::GetTaskRunnerFor(int worker_id) {
   base::AutoLock locker(task_runner_map_lock_);
-  return base::ContainsKey(task_runner_map_, worker_id)
+  return base::Contains(task_runner_map_, worker_id)
              ? task_runner_map_[worker_id]
              : task_runner_for_dead_worker_.get();
 }
diff --git a/content/shell/browser/web_test/blink_test_controller.cc b/content/shell/browser/web_test/blink_test_controller.cc
index 985690a..7943a8a 100644
--- a/content/shell/browser/web_test/blink_test_controller.cc
+++ b/content/shell/browser/web_test/blink_test_controller.cc
@@ -966,7 +966,7 @@
 
   // Is this the 1st time this renderer contains parts of the main test window?
   if (main_window &&
-      !base::ContainsKey(main_window_render_process_hosts_, process_host)) {
+      !base::Contains(main_window_render_process_hosts_, process_host)) {
     main_window_render_process_hosts_.insert(process_host);
 
     // Make sure the new renderer process_host has a test configuration shared
diff --git a/content/shell/test_runner/test_runner.cc b/content/shell/test_runner/test_runner.cc
index 5c2baad4..53aed265 100644
--- a/content/shell/test_runner/test_runner.cc
+++ b/content/shell/test_runner/test_runner.cc
@@ -1820,7 +1820,7 @@
   if (!IsFramePartOfMainTestWindow(frame))
     return;
 
-  if (!base::ContainsValue(loading_frames_, frame))
+  if (!base::Contains(loading_frames_, frame))
     return;
 
   DCHECK(web_test_runtime_flags_.have_loading_frame());
diff --git a/content/test/mock_clipboard_host.cc b/content/test/mock_clipboard_host.cc
index fab0d96..dfa82b1 100644
--- a/content/test/mock_clipboard_host.cc
+++ b/content/test/mock_clipboard_host.cc
@@ -43,7 +43,7 @@
   if (!image_.isNull())
     types.push_back(base::UTF8ToUTF16("image/png"));
   for (auto& it : custom_data_) {
-    CHECK(!base::ContainsValue(types, it.first));
+    CHECK(!base::Contains(types, it.first));
     types.push_back(it.first);
   }
   std::move(callback).Run(types, false);
diff --git a/content/test/web_contents_observer_sanity_checker.cc b/content/test/web_contents_observer_sanity_checker.cc
index f6d19ee..3e01d78 100644
--- a/content/test/web_contents_observer_sanity_checker.cc
+++ b/content/test/web_contents_observer_sanity_checker.cc
@@ -230,8 +230,8 @@
   // If ReadyToCommitNavigation was dispatched, verify that the
   // |navigation_handle| has the same RenderFrameHost at this time as the one
   // returned at ReadyToCommitNavigation.
-  if (base::ContainsKey(ready_to_commit_hosts_,
-                        navigation_handle->GetNavigationId())) {
+  if (base::Contains(ready_to_commit_hosts_,
+                     navigation_handle->GetNavigationId())) {
     CHECK_EQ(ready_to_commit_hosts_[navigation_handle->GetNavigationId()],
              navigation_handle->GetRenderFrameHost());
     ready_to_commit_hosts_.erase(navigation_handle->GetNavigationId());
@@ -283,7 +283,7 @@
     const MediaPlayerInfo& media_info,
     const MediaPlayerId& id) {
   CHECK(!web_contents_destroyed_);
-  CHECK(!base::ContainsValue(active_media_players_, id));
+  CHECK(!base::Contains(active_media_players_, id));
   active_media_players_.push_back(id);
 }
 
@@ -292,7 +292,7 @@
     const MediaPlayerId& id,
     WebContentsObserver::MediaStoppedReason reason) {
   CHECK(!web_contents_destroyed_);
-  CHECK(base::ContainsValue(active_media_players_, id));
+  CHECK(base::Contains(active_media_players_, id));
   base::Erase(active_media_players_, id);
 }
 
diff --git a/device/bluetooth/bluetooth_adapter_factory_wrapper.cc b/device/bluetooth/bluetooth_adapter_factory_wrapper.cc
index ca14603..26f264d 100644
--- a/device/bluetooth/bluetooth_adapter_factory_wrapper.cc
+++ b/device/bluetooth/bluetooth_adapter_factory_wrapper.cc
@@ -107,7 +107,7 @@
     BluetoothAdapter::Observer* observer) {
   DCHECK(thread_checker_.CalledOnValidThread());
 
-  return base::ContainsKey(adapter_observers_, observer);
+  return base::Contains(adapter_observers_, observer);
 }
 
 void BluetoothAdapterFactoryWrapper::AddAdapterObserver(
diff --git a/device/bluetooth/bluetooth_adapter_winrt.cc b/device/bluetooth/bluetooth_adapter_winrt.cc
index 48bd0eda..fbca322c 100644
--- a/device/bluetooth/bluetooth_adapter_winrt.cc
+++ b/device/bluetooth/bluetooth_adapter_winrt.cc
@@ -1298,7 +1298,7 @@
 void BluetoothAdapterWinrt::OnRegisterAdvertisement(
     BluetoothAdvertisement* advertisement,
     const CreateAdvertisementCallback& callback) {
-  DCHECK(base::ContainsValue(pending_advertisements_, advertisement));
+  DCHECK(base::Contains(pending_advertisements_, advertisement));
   auto wrapped_advertisement = base::WrapRefCounted(advertisement);
   base::Erase(pending_advertisements_, advertisement);
   callback.Run(std::move(wrapped_advertisement));
diff --git a/device/bluetooth/bluetooth_device_android.cc b/device/bluetooth/bluetooth_device_android.cc
index 4ef8ad8..6ec4c86 100644
--- a/device/bluetooth/bluetooth_device_android.cc
+++ b/device/bluetooth/bluetooth_device_android.cc
@@ -249,7 +249,7 @@
   std::string instance_id_string =
       base::android::ConvertJavaStringToUTF8(env, instance_id);
 
-  if (base::ContainsKey(gatt_services_, instance_id_string))
+  if (base::Contains(gatt_services_, instance_id_string))
     return;
 
   std::unique_ptr<BluetoothRemoteGattServiceAndroid> service =
diff --git a/device/bluetooth/bluetooth_device_unittest.cc b/device/bluetooth/bluetooth_device_unittest.cc
index e4fdd00..529eb2f 100644
--- a/device/bluetooth/bluetooth_device_unittest.cc
+++ b/device/bluetooth/bluetooth_device_unittest.cc
@@ -287,9 +287,8 @@
   EXPECT_EQ(base::UTF8ToUTF16(kTestDeviceName), device->GetNameForDisplay());
   EXPECT_FALSE(device->IsPaired());
   UUIDSet uuids = device->GetUUIDs();
-  EXPECT_TRUE(base::ContainsKey(uuids, BluetoothUUID(kTestUUIDGenericAccess)));
-  EXPECT_TRUE(
-      base::ContainsKey(uuids, BluetoothUUID(kTestUUIDGenericAttribute)));
+  EXPECT_TRUE(base::Contains(uuids, BluetoothUUID(kTestUUIDGenericAccess)));
+  EXPECT_TRUE(base::Contains(uuids, BluetoothUUID(kTestUUIDGenericAttribute)));
 }
 
 // Verifies that the device name can be populated by later advertisement
diff --git a/device/bluetooth/bluetooth_device_win_unittest.cc b/device/bluetooth/bluetooth_device_win_unittest.cc
index 30e8cd2..025092d 100644
--- a/device/bluetooth/bluetooth_device_win_unittest.cc
+++ b/device/bluetooth/bluetooth_device_win_unittest.cc
@@ -88,8 +88,8 @@
   BluetoothDevice::UUIDSet uuids = device_->GetUUIDs();
 
   EXPECT_EQ(2u, uuids.size());
-  EXPECT_TRUE(base::ContainsKey(uuids, kTestAudioSdpUuid));
-  EXPECT_TRUE(base::ContainsKey(uuids, kTestVideoSdpUuid));
+  EXPECT_TRUE(base::Contains(uuids, kTestAudioSdpUuid));
+  EXPECT_TRUE(base::Contains(uuids, kTestVideoSdpUuid));
 
   uuids = empty_device_->GetUUIDs();
   EXPECT_EQ(0u, uuids.size());
diff --git a/device/bluetooth/bluetooth_gatt_discoverer_winrt.cc b/device/bluetooth/bluetooth_gatt_discoverer_winrt.cc
index 14fd46a..ebd294e 100644
--- a/device/bluetooth/bluetooth_gatt_discoverer_winrt.cc
+++ b/device/bluetooth/bluetooth_gatt_discoverer_winrt.cc
@@ -303,8 +303,8 @@
     return;
   }
 
-  DCHECK(!base::ContainsKey(service_to_characteristics_map_,
-                            service_attribute_handle));
+  DCHECK(!base::Contains(service_to_characteristics_map_,
+                         service_attribute_handle));
   auto& characteristics_list =
       service_to_characteristics_map_[service_attribute_handle];
   if (!GetAsVector(characteristics.Get(), &characteristics_list)) {
@@ -375,8 +375,8 @@
     return;
   }
 
-  DCHECK(!base::ContainsKey(characteristic_to_descriptors_map_,
-                            characteristic_attribute_handle));
+  DCHECK(!base::Contains(characteristic_to_descriptors_map_,
+                         characteristic_attribute_handle));
   if (!GetAsVector(descriptors.Get(), &characteristic_to_descriptors_map_
                                           [characteristic_attribute_handle])) {
     std::move(callback_).Run(false);
diff --git a/device/bluetooth/bluetooth_remote_gatt_characteristic_android.cc b/device/bluetooth/bluetooth_remote_gatt_characteristic_android.cc
index 0e76e676..50415ac 100644
--- a/device/bluetooth/bluetooth_remote_gatt_characteristic_android.cc
+++ b/device/bluetooth/bluetooth_remote_gatt_characteristic_android.cc
@@ -236,7 +236,7 @@
   std::string instanceIdString =
       base::android::ConvertJavaStringToUTF8(env, instanceId);
 
-  DCHECK(!base::ContainsKey(descriptors_, instanceIdString));
+  DCHECK(!base::Contains(descriptors_, instanceIdString));
   AddDescriptor(BluetoothRemoteGattDescriptorAndroid::Create(
       instanceIdString, bluetooth_gatt_descriptor_wrapper,
       chrome_bluetooth_device));
diff --git a/device/bluetooth/bluetooth_remote_gatt_service_android.cc b/device/bluetooth/bluetooth_remote_gatt_service_android.cc
index a8e3957d..bf31b68 100644
--- a/device/bluetooth/bluetooth_remote_gatt_service_android.cc
+++ b/device/bluetooth/bluetooth_remote_gatt_service_android.cc
@@ -176,7 +176,7 @@
   std::string instance_id_string =
       base::android::ConvertJavaStringToUTF8(env, instance_id);
 
-  DCHECK(!base::ContainsKey(characteristics_, instance_id_string));
+  DCHECK(!base::Contains(characteristics_, instance_id_string));
   AddCharacteristic(BluetoothRemoteGattCharacteristicAndroid::Create(
       adapter_, this, instance_id_string, bluetooth_gatt_characteristic_wrapper,
       chrome_bluetooth_device));
diff --git a/device/bluetooth/bluetooth_remote_gatt_service_win.cc b/device/bluetooth/bluetooth_remote_gatt_service_win.cc
index 24ab750..8d0b5b9 100644
--- a/device/bluetooth/bluetooth_remote_gatt_service_win.cc
+++ b/device/bluetooth/bluetooth_remote_gatt_service_win.cc
@@ -84,7 +84,7 @@
 void BluetoothRemoteGattServiceWin::GattCharacteristicDiscoveryComplete(
     BluetoothRemoteGattCharacteristicWin* characteristic) {
   DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
-  DCHECK(base::ContainsKey(characteristics_, characteristic->GetIdentifier()));
+  DCHECK(base::Contains(characteristics_, characteristic->GetIdentifier()));
 
   discovery_completed_included_characteristics_.insert(
       characteristic->GetIdentifier());
@@ -227,7 +227,7 @@
   // characteristic's destructor. This will ensure that any call to
   // GetCharacteristics() won't contain an entry corresponding to |identifier|.
   // Note: `characteristics_.erase(identifier);` would not guarantee this.
-  DCHECK(base::ContainsKey(characteristics_, identifier));
+  DCHECK(base::Contains(characteristics_, identifier));
   auto iter = characteristics_.find(identifier);
   auto pair = std::move(*iter);
   characteristics_.erase(iter);
diff --git a/device/bluetooth/bluez/bluetooth_adapter_bluez.cc b/device/bluetooth/bluez/bluetooth_adapter_bluez.cc
index 030b34eb..55dd9dd 100644
--- a/device/bluetooth/bluez/bluetooth_adapter_bluez.cc
+++ b/device/bluetooth/bluez/bluetooth_adapter_bluez.cc
@@ -449,7 +449,7 @@
 
       UUIDSet intersection;
       for (const BluetoothUUID& uuid : filter_uuids) {
-        if (base::ContainsKey(device_uuids, uuid)) {
+        if (base::Contains(device_uuids, uuid)) {
           intersection.insert(uuid);
         }
       }
diff --git a/device/bluetooth/bluez/bluetooth_bluez_unittest.cc b/device/bluetooth/bluez/bluetooth_bluez_unittest.cc
index 9773d8e..b35e200 100644
--- a/device/bluetooth/bluez/bluetooth_bluez_unittest.cc
+++ b/device/bluetooth/bluez/bluetooth_bluez_unittest.cc
@@ -1441,7 +1441,7 @@
   EXPECT_EQ(-60, *filter->rssi);
   EXPECT_EQ(nullptr, filter->pathloss.get());
   std::vector<std::string> uuids = *filter->uuids;
-  EXPECT_TRUE(base::ContainsValue(uuids, "1000"));
+  EXPECT_TRUE(base::Contains(uuids, "1000"));
 
   discovery_sessions_[0]->Stop(
       base::Bind(&BluetoothBlueZTest::Callback, base::Unretained(this)),
@@ -1569,8 +1569,8 @@
   EXPECT_EQ(-65, *filter->rssi);
   EXPECT_EQ(nullptr, filter->pathloss.get());
   auto uuids = *filter->uuids;
-  EXPECT_TRUE(base::ContainsValue(uuids, "1000"));
-  EXPECT_TRUE(base::ContainsValue(uuids, "1002"));
+  EXPECT_TRUE(base::Contains(uuids, "1000"));
+  EXPECT_TRUE(base::Contains(uuids, "1002"));
 
   discovery_sessions_[0]->Stop(
       base::Bind(&BluetoothBlueZTest::Callback, base::Unretained(this)),
@@ -1674,7 +1674,7 @@
   EXPECT_EQ(-65, *filter->rssi);
   EXPECT_EQ(nullptr, filter->pathloss.get());
   auto uuids = *filter->uuids;
-  EXPECT_TRUE(base::ContainsValue(uuids, "1002"));
+  EXPECT_TRUE(base::Contains(uuids, "1002"));
 
   discovery_sessions_[0]->Stop(
       base::Bind(&BluetoothBlueZTest::Callback, base::Unretained(this)),
@@ -1752,7 +1752,7 @@
   EXPECT_EQ(-60, *filter->rssi);
   EXPECT_EQ(nullptr, filter->pathloss.get());
   std::vector<std::string> uuids = *filter->uuids;
-  EXPECT_TRUE(base::ContainsValue(uuids, "1000"));
+  EXPECT_TRUE(base::Contains(uuids, "1000"));
 
   discovery_sessions_[0]->Stop(
       base::Bind(&BluetoothBlueZTest::Callback, base::Unretained(this)),
@@ -1830,26 +1830,26 @@
       EXPECT_EQ(-85, *filter->rssi);
       EXPECT_EQ(nullptr, filter->pathloss.get());
       std::vector<std::string> uuids = *filter->uuids;
-      EXPECT_TRUE(base::ContainsValue(uuids, "1000"));
+      EXPECT_TRUE(base::Contains(uuids, "1000"));
     } else if (i == 1) {
       auto* filter = fake_bluetooth_adapter_client_->GetDiscoveryFilter();
       EXPECT_EQ("le", *filter->transport);
       EXPECT_EQ(-85, *filter->rssi);
       EXPECT_EQ(nullptr, filter->pathloss.get());
       std::vector<std::string> uuids = *filter->uuids;
-      EXPECT_TRUE(base::ContainsValue(uuids, "1000"));
-      EXPECT_TRUE(base::ContainsValue(uuids, "1001"));
-      EXPECT_TRUE(base::ContainsValue(uuids, "1020"));
+      EXPECT_TRUE(base::Contains(uuids, "1000"));
+      EXPECT_TRUE(base::Contains(uuids, "1001"));
+      EXPECT_TRUE(base::Contains(uuids, "1020"));
     } else if (i == 2) {
       auto* filter = fake_bluetooth_adapter_client_->GetDiscoveryFilter();
       EXPECT_EQ("le", *filter->transport);
       EXPECT_EQ(-85, *filter->rssi);
       EXPECT_EQ(nullptr, filter->pathloss.get());
       std::vector<std::string> uuids = *filter->uuids;
-      EXPECT_TRUE(base::ContainsValue(uuids, "1000"));
-      EXPECT_TRUE(base::ContainsValue(uuids, "1001"));
-      EXPECT_TRUE(base::ContainsValue(uuids, "1003"));
-      EXPECT_TRUE(base::ContainsValue(uuids, "1020"));
+      EXPECT_TRUE(base::Contains(uuids, "1000"));
+      EXPECT_TRUE(base::Contains(uuids, "1001"));
+      EXPECT_TRUE(base::Contains(uuids, "1003"));
+      EXPECT_TRUE(base::Contains(uuids, "1020"));
     }
   }
 
@@ -1875,10 +1875,10 @@
       EXPECT_EQ(nullptr, filter->pathloss.get());
       std::vector<std::string> uuids = *filter->uuids;
       EXPECT_EQ(3UL, uuids.size());
-      EXPECT_FALSE(base::ContainsValue(uuids, "1000"));
-      EXPECT_TRUE(base::ContainsValue(uuids, "1001"));
-      EXPECT_TRUE(base::ContainsValue(uuids, "1003"));
-      EXPECT_TRUE(base::ContainsValue(uuids, "1020"));
+      EXPECT_FALSE(base::Contains(uuids, "1000"));
+      EXPECT_TRUE(base::Contains(uuids, "1001"));
+      EXPECT_TRUE(base::Contains(uuids, "1003"));
+      EXPECT_TRUE(base::Contains(uuids, "1020"));
     } else if (i == 1) {
       auto* filter = fake_bluetooth_adapter_client_->GetDiscoveryFilter();
       EXPECT_EQ("le", *filter->transport);
@@ -1886,10 +1886,10 @@
       EXPECT_EQ(nullptr, filter->pathloss.get());
       std::vector<std::string> uuids = *filter->uuids;
       EXPECT_EQ(2UL, uuids.size());
-      EXPECT_FALSE(base::ContainsValue(uuids, "1000"));
-      EXPECT_FALSE(base::ContainsValue(uuids, "1001"));
-      EXPECT_TRUE(base::ContainsValue(uuids, "1003"));
-      EXPECT_TRUE(base::ContainsValue(uuids, "1020"));
+      EXPECT_FALSE(base::Contains(uuids, "1000"));
+      EXPECT_FALSE(base::Contains(uuids, "1001"));
+      EXPECT_TRUE(base::Contains(uuids, "1003"));
+      EXPECT_TRUE(base::Contains(uuids, "1020"));
     } else if (i == 2) {
       auto* filter = fake_bluetooth_adapter_client_->GetDiscoveryFilter();
       EXPECT_EQ("le", *filter->transport);
@@ -1952,19 +1952,19 @@
       EXPECT_EQ(-85, *filter->rssi);
       EXPECT_EQ(nullptr, filter->pathloss.get());
       std::vector<std::string> uuids = *filter->uuids;
-      EXPECT_TRUE(base::ContainsValue(uuids, "1000"));
-      EXPECT_TRUE(base::ContainsValue(uuids, "1003"));
-      EXPECT_TRUE(base::ContainsValue(uuids, "1020"));
+      EXPECT_TRUE(base::Contains(uuids, "1000"));
+      EXPECT_TRUE(base::Contains(uuids, "1003"));
+      EXPECT_TRUE(base::Contains(uuids, "1020"));
     } else if (i == 1 || i == 2) {
       auto* filter = fake_bluetooth_adapter_client_->GetDiscoveryFilter();
       EXPECT_EQ("le", *filter->transport);
       EXPECT_EQ(-85, *filter->rssi);
       EXPECT_EQ(nullptr, filter->pathloss.get());
       std::vector<std::string> uuids = *filter->uuids;
-      EXPECT_TRUE(base::ContainsValue(uuids, "1000"));
-      EXPECT_TRUE(base::ContainsValue(uuids, "1001"));
-      EXPECT_TRUE(base::ContainsValue(uuids, "1003"));
-      EXPECT_TRUE(base::ContainsValue(uuids, "1020"));
+      EXPECT_TRUE(base::Contains(uuids, "1000"));
+      EXPECT_TRUE(base::Contains(uuids, "1001"));
+      EXPECT_TRUE(base::Contains(uuids, "1003"));
+      EXPECT_TRUE(base::Contains(uuids, "1020"));
     }
   }
 
@@ -2032,7 +2032,7 @@
   EXPECT_EQ(-15, *filter->rssi);
   EXPECT_EQ(nullptr, filter->pathloss.get());
   std::vector<std::string> uuids = *filter->uuids;
-  EXPECT_TRUE(base::ContainsValue(uuids, "1000"));
+  EXPECT_TRUE(base::Contains(uuids, "1000"));
 
   df = new BluetoothDiscoveryFilter(device::BLUETOOTH_TRANSPORT_LE);
   df->SetRSSI(-60);
@@ -2053,9 +2053,9 @@
   EXPECT_EQ(-60, *filter->rssi);
   EXPECT_EQ(nullptr, filter->pathloss.get());
   uuids = *filter->uuids;
-  EXPECT_TRUE(base::ContainsValue(uuids, "1000"));
-  EXPECT_TRUE(base::ContainsValue(uuids, "1001"));
-  EXPECT_TRUE(base::ContainsValue(uuids, "1020"));
+  EXPECT_TRUE(base::Contains(uuids, "1000"));
+  EXPECT_TRUE(base::Contains(uuids, "1001"));
+  EXPECT_TRUE(base::Contains(uuids, "1020"));
 
   BluetoothDiscoveryFilter* df3 =
       new BluetoothDiscoveryFilter(device::BLUETOOTH_TRANSPORT_CLASSIC);
@@ -2077,10 +2077,10 @@
   EXPECT_EQ(-65, *filter->rssi);
   EXPECT_EQ(nullptr, filter->pathloss.get());
   uuids = *filter->uuids;
-  EXPECT_TRUE(base::ContainsValue(uuids, "1000"));
-  EXPECT_TRUE(base::ContainsValue(uuids, "1001"));
-  EXPECT_TRUE(base::ContainsValue(uuids, "1003"));
-  EXPECT_TRUE(base::ContainsValue(uuids, "1020"));
+  EXPECT_TRUE(base::Contains(uuids, "1000"));
+  EXPECT_TRUE(base::Contains(uuids, "1001"));
+  EXPECT_TRUE(base::Contains(uuids, "1003"));
+  EXPECT_TRUE(base::Contains(uuids, "1020"));
 
   // start additionally classic scan
   adapter_->StartDiscoverySession(
@@ -2133,8 +2133,8 @@
 
   BluetoothDevice::UUIDSet uuids = devices[idx]->GetUUIDs();
   EXPECT_EQ(2U, uuids.size());
-  EXPECT_TRUE(base::ContainsKey(uuids, BluetoothUUID("1800")));
-  EXPECT_TRUE(base::ContainsKey(uuids, BluetoothUUID("1801")));
+  EXPECT_TRUE(base::Contains(uuids, BluetoothUUID("1800")));
+  EXPECT_TRUE(base::Contains(uuids, BluetoothUUID("1801")));
 
   EXPECT_EQ(BluetoothDevice::VENDOR_ID_USB, devices[idx]->GetVendorIDSource());
   EXPECT_EQ(0x05ac, devices[idx]->GetVendorID());
@@ -2504,8 +2504,8 @@
 
   BluetoothDevice::UUIDSet uuids = devices[idx]->GetUUIDs();
   ASSERT_EQ(2U, uuids.size());
-  ASSERT_TRUE(base::ContainsKey(uuids, BluetoothUUID("1800")));
-  ASSERT_TRUE(base::ContainsKey(uuids, BluetoothUUID("1801")));
+  ASSERT_TRUE(base::Contains(uuids, BluetoothUUID("1800")));
+  ASSERT_TRUE(base::Contains(uuids, BluetoothUUID("1801")));
 
   // Install an observer; expect the DeviceChanged method to be called when
   // we change the class of the device.
@@ -2530,11 +2530,11 @@
   // Fetching the value should give the new one.
   uuids = devices[idx]->GetUUIDs();
   EXPECT_EQ(5U, uuids.size());
-  EXPECT_TRUE(base::ContainsKey(uuids, BluetoothUUID("1800")));
-  EXPECT_TRUE(base::ContainsKey(uuids, BluetoothUUID("1801")));
-  EXPECT_TRUE(base::ContainsKey(uuids, BluetoothUUID("110c")));
-  EXPECT_TRUE(base::ContainsKey(uuids, BluetoothUUID("110e")));
-  EXPECT_TRUE(base::ContainsKey(uuids, BluetoothUUID("110a")));
+  EXPECT_TRUE(base::Contains(uuids, BluetoothUUID("1800")));
+  EXPECT_TRUE(base::Contains(uuids, BluetoothUUID("1801")));
+  EXPECT_TRUE(base::Contains(uuids, BluetoothUUID("110c")));
+  EXPECT_TRUE(base::Contains(uuids, BluetoothUUID("110e")));
+  EXPECT_TRUE(base::Contains(uuids, BluetoothUUID("110a")));
 }
 
 TEST_F(BluetoothBlueZTest, DeviceInquiryRSSIInvalidated) {
@@ -2771,7 +2771,7 @@
   // Verify is a HID device and is not connectable.
   BluetoothDevice::UUIDSet uuids = device->GetUUIDs();
   EXPECT_EQ(1U, uuids.size());
-  EXPECT_TRUE(base::ContainsKey(uuids, BluetoothUUID("1124")));
+  EXPECT_TRUE(base::Contains(uuids, BluetoothUUID("1124")));
   EXPECT_FALSE(device->IsConnectable());
 }
 
@@ -3074,7 +3074,7 @@
   // Verify is a HID device and is connectable.
   BluetoothDevice::UUIDSet uuids = device->GetUUIDs();
   EXPECT_EQ(1U, uuids.size());
-  EXPECT_TRUE(base::ContainsKey(uuids, BluetoothUUID("1124")));
+  EXPECT_TRUE(base::Contains(uuids, BluetoothUUID("1124")));
   EXPECT_TRUE(device->IsConnectable());
 
   // Make sure the trusted property has been set to true.
@@ -3131,7 +3131,7 @@
   // Verify is a HID device and is connectable.
   BluetoothDevice::UUIDSet uuids = device->GetUUIDs();
   EXPECT_EQ(1U, uuids.size());
-  EXPECT_TRUE(base::ContainsKey(uuids, BluetoothUUID("1124")));
+  EXPECT_TRUE(base::Contains(uuids, BluetoothUUID("1124")));
   EXPECT_TRUE(device->IsConnectable());
 
   // Make sure the trusted property has been set to true.
@@ -3208,7 +3208,7 @@
   // Verify is a HID device.
   BluetoothDevice::UUIDSet uuids = device->GetUUIDs();
   EXPECT_EQ(1U, uuids.size());
-  EXPECT_TRUE(base::ContainsKey(uuids, BluetoothUUID("1124")));
+  EXPECT_TRUE(base::Contains(uuids, BluetoothUUID("1124")));
 
   // And usually not connectable.
   EXPECT_FALSE(device->IsConnectable());
diff --git a/device/bluetooth/chromeos/bluetooth_utils.cc b/device/bluetooth/chromeos/bluetooth_utils.cc
index 8b9098c9..69194dd4e 100644
--- a/device/bluetooth/chromeos/bluetooth_utils.cc
+++ b/device/bluetooth/chromeos/bluetooth_utils.cc
@@ -77,10 +77,10 @@
       // For LE devices, check the service UUID to determine if it supports HID
       // or second factor authenticator (security key).
       case BLUETOOTH_TRANSPORT_LE:
-        if (base::ContainsKey(device->GetUUIDs(),
-                              device::BluetoothUUID(kHIDServiceUUID)) ||
-            base::ContainsKey(device->GetUUIDs(),
-                              device::BluetoothUUID(kSecurityKeyServiceUUID))) {
+        if (base::Contains(device->GetUUIDs(),
+                           device::BluetoothUUID(kHIDServiceUUID)) ||
+            base::Contains(device->GetUUIDs(),
+                           device::BluetoothUUID(kSecurityKeyServiceUUID))) {
           result.push_back(device);
         }
         break;
diff --git a/device/bluetooth/dbus/fake_bluetooth_device_client.cc b/device/bluetooth/dbus/fake_bluetooth_device_client.cc
index 1d2c887f..d97dec0 100644
--- a/device/bluetooth/dbus/fake_bluetooth_device_client.cc
+++ b/device/bluetooth/dbus/fake_bluetooth_device_client.cc
@@ -711,7 +711,7 @@
 void FakeBluetoothDeviceClient::CreateDevice(
     const dbus::ObjectPath& adapter_path,
     const dbus::ObjectPath& device_path) {
-  if (base::ContainsValue(device_list_, device_path))
+  if (base::Contains(device_list_, device_path))
     return;
 
   std::unique_ptr<Properties> properties(
@@ -846,7 +846,7 @@
     const dbus::ObjectPath& adapter_path,
     const IncomingDeviceProperties& props) {
   dbus::ObjectPath device_path(props.device_path);
-  if (base::ContainsValue(device_list_, device_path))
+  if (base::Contains(device_list_, device_path))
     return;
 
   std::unique_ptr<Properties> properties(
@@ -1902,7 +1902,7 @@
     base::Base64Encode(base::RandBytesAsString(10), &id);
     base::RemoveChars(id, "+/=", &id);
     device_path = dbus::ObjectPath(adapter_path.value() + "/dev" + id);
-  } while (base::ContainsValue(device_list_, device_path));
+  } while (base::Contains(device_list_, device_path));
 
   std::unique_ptr<Properties> properties(
       new Properties(base::Bind(&FakeBluetoothDeviceClient::OnPropertyChanged,
diff --git a/device/bluetooth/dbus/fake_bluetooth_media_client.cc b/device/bluetooth/dbus/fake_bluetooth_media_client.cc
index d33f380..e124b6bf 100644
--- a/device/bluetooth/dbus/fake_bluetooth_media_client.cc
+++ b/device/bluetooth/dbus/fake_bluetooth_media_client.cc
@@ -80,7 +80,7 @@
   // TODO(mcchou): Come up with some corresponding actions.
   VLOG(1) << "UnregisterEndpoint: " << endpoint_path.value();
 
-  if (!base::ContainsKey(endpoints_, endpoint_path)) {
+  if (!base::Contains(endpoints_, endpoint_path)) {
     error_callback.Run(kFailedError, "Unknown media endpoint");
     return;
   }
@@ -130,7 +130,7 @@
 
 bool FakeBluetoothMediaClient::IsRegistered(
     const dbus::ObjectPath& endpoint_path) {
-  return base::ContainsKey(endpoints_, endpoint_path);
+  return base::Contains(endpoints_, endpoint_path);
 }
 
 }  // namespace bluez
diff --git a/docs/testing/writing_js_unit_tests.md b/docs/testing/writing_js_unit_tests.md
new file mode 100644
index 0000000..50ad875
--- /dev/null
+++ b/docs/testing/writing_js_unit_tests.md
@@ -0,0 +1,378 @@
+<!--* freshness: { owner: 'olsen' reviewed: '2019-06-07' } *-->
+
+<!-- Originally published June 7, 2019 -->
+
+# Writing a JavaScript unit test
+
+You have written some JS code and put it somewhere in chromium src. Now you want
+to write unit tests for it.
+
+[TOC]
+
+## Testing JS code with no dependencies
+
+Let's say you have written a file `awesome.js`. Next you will write your unit
+tests in a file in the same directory called `awesome_test.unitjs`. The
+`.unitjs` suffix simply means a javascript unit test. (There are a couple of
+other suffix options, but .unitjs is the most common, and it will do for most
+purposes).
+
+### Writing the test itself
+
+Here is an example to show how your test might look:
+
+```js
+/** Tests for awesome.js */
+
+GEN_INCLUDE([
+  'awesome.js'  // Include the file under test.
+]);
+
+function AwesomeUnitTest() {}
+
+AwesomeUnitTest.prototype = {
+  __proto__: testing.Test.prototype
+};
+
+const EXPECTED_AWESOME_NUMBER = 1e6;
+
+TEST_F(AwesomeUnitTest, 'HowAwesomeExactly', function () {
+  // Some asserts to make sure the file under test is working as expected.
+  assertEquals(EXPECTED_AWESOME_NUMBER, awesome.find_awesome_number(), 'If this fails, contact Larry');
+});
+```
+
+Since ECMAScript 6, there is an alternative but equivalent syntax that uses the
+class keyword instead of prototype. Codesearch shows that existing unitjs tests
+use either syntax.
+
+### Writing the BUILD rules
+
+Next you need to add a
+[js2gtest](https://cs.chromium.org/chromium/src/chrome/test/base/js2gtest.gni)
+rule in the same package. Let's say it's called `package_one`. Here is an
+example to show how your BUILD rules might look:
+
+```build
+js2gtest("package_one_unitjs_tests") {
+  test_type = "unit"
+  sources = [
+    "awesome_test.unitjs",
+    # ... for simplicity, put all .unitjs files in this package into this one rule.
+  ]
+  gen_include_files = [
+    "awesome.js",
+  ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  deps = [":package_one_unitjs_tests"]
+}
+```
+
+And finally, you will have to modify the huge `//chrome/test:unit_tests` build
+rule to depend (directly or indirectly) on your new `//package_one:unit_tests`
+target.
+
+Once this is done, you can build and run the tests like normal:
+
+```shell
+chromium/src$ out/Default/unit_tests --gtest_filter="AwesomeUnitTest*"
+```
+
+## Testing JS code which depends on another library
+
+You may have some other way that the dependencies are being correctly included
+in production. However, it will probably be simplest not to try and make the
+`js2gtest` rule recognize the dependency management system you are using, but
+instead just declare all JS dependencies explicitly when writing the test. The
+way to do this is by adding the needed JS source files to both `GEN_INCLUDE` in
+the test and `gen_include_files` in the BUILD rule. These files will be included
+in the order specified in the JS file. These files that are included can also
+have their own calls to GEN_INCLUDE to transitively include other files, if
+needed - these would then all need to be listed in the gen_include_files list in
+the BUILD rule.
+
+### Writing a test which depends on another library
+
+```js {highlight="lines:4"}
+/** Tests for awesome.js */
+
+GEN_INCLUDE([
+  '//some/dependency.js',
+  'awesome.js',
+]);
+
+function AwesomeUnitTest() {}
+// ... etc ...
+```
+
+### Writing the BUILD rule for a test that depends on another library
+
+```build {highlight="lines:7"}
+js2gtest("package_one_unitjs_tests") {
+  test_type = "unit"
+  sources = [
+    "awesome_test.unitjs",
+  ]
+  gen_include_files = [
+    "//some/dependency.js",
+    "awesome.js",
+  ]
+}
+# ... etc ...
+```
+
+## Testing JS code which depends on the browser / the DOM {#browser-dep}
+
+The tests above are run using a V8 interpreter. However, they are not run from
+within a browser. That means all JS language features are available, but no
+browser features are available. Specifically, the browser context (the global
+object called `window`) is left undefined, and anything to do with the DOM, the
+UI, rendering, parsing, event handling, mouse clicks, HTML, XML, SVG, canvas,
+etc is not supported. It may be that you need some of this - perhaps you are
+trying to write and test a web-based UI, or perhaps your code just expects an
+XML parser to be available.
+
+If you are creating an web-based UI, what you are now writing is called a
+`webui` test. Ideally, you should read documentation specifically for writing
+and testing web UI components.
+
+FIXME: Is there any documentation for writing and testing web UI components?
+
+If on the other hand, you are writing some JS functionality that just happens to
+use a feature that is part of the browser, and not the language (such as the XML
+parser), you should follow the instructions in this section. The current
+best-practice is to write your unit test as before, but to declare it as a
+`webui` test and add it to the `browser_tests` build rule. Ideally there would
+be a different category for unit tests that don't have any UI and so aren't
+webui, but simply need a particular browser feature, but using `webui` works for
+now.
+
+### Changes to your test to make it a webui test
+
+```js {highlight="lines:4,7"}
+AwesomeUnitTest.prototype = {
+  __proto__: testing.Test.prototype,
+
+  browsePreload: DUMMY_URL,
+
+  // No need to run these checks unless you are testing an actual user interface.
+  runAccessibilityChecks: false,
+};
+```
+
+### Changes to your build rule to make it a webui test
+
+```build {highlight="lines:2,9,12"}
+js2gtest("package_one_unitjs_tests") {
+  test_type = "webui"
+  sources = [
+    "awesome_test.unitjs",
+  ]
+  gen_include_files = [
+    "awesome.js",
+  ]
+  defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
+}
+
+source_set("browser_tests") {
+  testonly = true
+  deps = [":package_one_unitjs_tests"]
+}
+```
+
+FIXME: It might be nice if "HAS_OUT_OF_PROC_TEST_RUNNER" was automatically
+inferred from the test type.
+
+And the final change is to remove your test from the `//chrome/test:unit_tests`
+build rule, and add it instead to the `//chrome/test:browser_tests`. As you
+would expect, you now run your test like so:
+
+```shell
+chromium/src$ out/Default/browser_tests --gtest_filter="AwesomeUnitTest*"
+```
+
+And now the browser context and global `window` object should be available in
+your test.
+
+Note: If your test is declared as a `unit` test, it must be part of
+`//chrome/test:unit_tests`, and if your test is declared as a `webui` test, it
+must be part of `//chrome/test:browser_tests`. If your test is included in the
+wrong build rule, it will not compile, since it will be missing some necessary
+dependencies.
+
+## Troubleshooting
+
+### js2gtest.js: Error reading file
+
+Perhaps one of the files you are trying to read does not exist, has the wrong
+name, or has not been properly declared in the list of `gen_include_files` in
+`BUILD.gn`. The file that could not be found or read should be part of the error
+message, but if for some reason it is not clear, you can narrow it down by
+removing files from the `GEN_INCLUDE` directive one by one.
+
+### ReferenceError: window is not defined
+
+Or: document is not defined, DOMParser is not defined, frames is not defined,
+alert is not defined...
+
+This sounds like your unit test depends on some functionality that is part of
+the browser, and not part of the JS language itself. See the
+[section about depending on the browser](#browser-dep)
+
+### Test passes locally but fails on the commit queue
+
+This is probably due to a dependency not being properly declared - for instance,
+you have a file in the `GEN_INCLUDE` directive, that is not included in the list
+of `gen_include_files` in `BUILD.gn`, When run locally, the test may be able to
+find the appropriate file, but the swarming bots that run the submit queue will
+not necessarily be able to find files that have not been explicitly declared as
+dependencies.
+
+You can try running your test locally but in a more isolated way, so as to
+reproduce the problem locally. Something like:
+
+```shell
+chromium/src$ tools/mb/mb.py run //out/Default browser_tests -- --gtest_filter="AwesomeUnitTest*"
+```
+
+### Duplicate output file {#duplicate}
+
+The `js2gtest` rule copies various JS files to testdata, where they are read as
+data when the test is run. This causes problems if two different js2gtest rule
+instances both try to copy the same file to the same place in testdata. Having
+two rules both copy the same source file to the same destination is a build
+error - every file must be written only once.
+
+It should be safe to have multiple js2gtest rules which have files in common in
+the `gen_include_files` list, since these files are not copied. But, it will
+cause a build error to have multiple js2gtest rules which have files in common
+in either the `sources` list or the `extra_js_files` list, since all of these
+files are copied to testdata.
+
+To avoid this, only create one js2gtest rule per package which has all the
+necessary sources in, and to include files from outside the package, use
+gen_include_files and not extra_js_files.
+
+The duplicate output file build error could look something like this:
+
+```
+ERROR at //chrome/test/base/js2gtest.gni: Duplicate output file.
+    copy(copy_target_name) {
+    ^-----------------------
+Two or more targets generate the same output:
+  test_data/ui/webui/resources/js/cr.js
+```
+
+If you encounter such a build error and fix it, you could still end up with
+warnings on subsequent builds - something like the following:
+
+```
+warning: multiple rules generate test_data/ui/webui/resources/js/cr.js. builds involving this target will not be correct; continuing anyway [-w dupbuild=warn]
+```
+
+Get rid of this warning by doing a clean build.
+
+## Advanced topics
+
+### GEN_INCLUDE alternative
+
+There is an alternative to `gen_include_files` that is called `extra_js_files`.
+These are JS files that are not read during compilation, but are loaded at
+runtime. Because they are not read at compile time, they can contain no
+directives to be executed at compile time.
+
+Compare this to `gen_include_files` which are both read at compile time, and
+included at runtime. This means they support compile time directives like `GEN`
+and `GEN_INCLUDE` which can generate code and include further files - and those
+further files could have their own directives, and so on.
+
+Whether or not compile time directives are needed or used, these two rules work
+differently - extra_js_files copies source files into the `testdata` directory,
+but gen_include_files leaves the source files where they are.
+
+Warning: Avoid using `extra_js_files` because all files listed there are copied
+into the testdata directory, which can easily lead to the "duplicate output
+file" build error described in the [previous section](#duplicate).
+
+However, if you find you need to use extra_js_files instead of
+gen_include_files, this is how it is done:
+
+#### Changes to your test to make it use extra_js_files
+
+```js {highlight="lines:4-7"}
+AwesomeUnitTest.prototype = {
+  __proto__: testing.Test.prototype,
+
+  extraLibraries: [
+    "awesome.js",
+    "//some/dependency.js",
+  ],
+};
+```
+
+#### Changes to your build rule to make it use extra_js_files
+
+```build {highlight="lines:6"}
+js2gtest("package_one_unitjs_tests") {
+  test_type = "unit"
+  sources = [
+    "awesome_test.unitjs",
+  ]
+  extra_js_files = [
+    "awesome.js",
+    "//some/dependency.js"
+  ]
+}
+```
+
+### Including custom C++ code in the generated C++ test
+
+There are a number of ways to add to the C++ code that is generated by your
+.unitjs file.
+
+Calling
+[GEN(code)](https://cs.chromium.org/chromium/src/chrome/test/base/js2gtest.js?q=function.GEN%5C%28)
+in your .unitjs file will cause the C++ code to be included verbatim in the
+generated C++ test.
+
+The function `TEST_F` (or alternative form `TEST_F_WITH_PREAMBLE`) can take a
+[preamble](https://cs.chromium.org/chromium/src/chrome/test/base/js2gtest.js?q=preamble)
+argument - the preamble is extra C++ code that is included in the generated C++
+test, immediately before the code which makes the interpreter run the JS part of
+the test.
+
+You can define functions on your JS Test prototype, `testGenPreamble()` and
+`testGenPostamble()`. The testGenPreamble function is used to generate code that
+is included immediately before the JS part of your test, and the
+testGenPostamble function is used to generate code that goes immediately after -
+so you could for example put an "#ifdef" in the preamble and an "#endif" in the
+postamble. If you define these functions, they should call GEN to output the
+necessary C++ code.
+
+Since these are relatively complex to use, it may help you to codesearch for
+examples. Here are some useful examples:
+
+[oobe_webui_browsertest.js](https://cs.chromium.org/chromium/src/chrome/test/data/chromeos/oobe_webui_browsertest.js) -
+makes the browser fullscreen before running the JS test.
+
+[saml_password_attributes_test.unitjs](https://cs.chromium.org/chromium/src/chrome/browser/resources/chromeos/login/saml_password_attributes_test.unitjs) -
+loads an XML file into a JS global variable before running the JS test.
+
+Another thing that will make your life a little bit easier is being able to
+check the C++ code that is generated. Look for it in
+`out/Default/path/to/package_one/awesome_test-gen.cc` or equivalent.
+
+Warning: Overuse of custom C++ code can make your test difficult to understand -
+at this point you are running JS, that generates custom C++ - which will much
+later be compiled and executed, in order to help set up the test environment,
+within which the JS test itself will be run.
+
+Tip: To avoid this complexity, consider if it would be clearer to simply write a
+C++ test directly, instead of generating the C++ test from a JS file. You can
+always run JS code from a C++ test, even if the C++ test was not generated from
+a JS file. You could even use the generated output from a js2gtest rule as a
+starting point for writing a custom C++ test which you then check in.
+
diff --git a/extensions/browser/api/audio/audio_service_chromeos.cc b/extensions/browser/api/audio/audio_service_chromeos.cc
index c35e1cb..1128ab2 100644
--- a/extensions/browser/api/audio/audio_service_chromeos.cc
+++ b/extensions/browser/api/audio/audio_service_chromeos.cc
@@ -200,10 +200,10 @@
 
   bool accept_input =
       !(filter && filter->stream_types) ||
-      base::ContainsValue(*filter->stream_types, api::audio::STREAM_TYPE_INPUT);
-  bool accept_output = !(filter && filter->stream_types) ||
-                       base::ContainsValue(*filter->stream_types,
-                                           api::audio::STREAM_TYPE_OUTPUT);
+      base::Contains(*filter->stream_types, api::audio::STREAM_TYPE_INPUT);
+  bool accept_output =
+      !(filter && filter->stream_types) ||
+      base::Contains(*filter->stream_types, api::audio::STREAM_TYPE_OUTPUT);
 
   for (const auto& device : devices) {
     if (filter && filter->is_active && *filter->is_active != device.active)
diff --git a/extensions/browser/api/bluetooth/bluetooth_event_router.cc b/extensions/browser/api/bluetooth/bluetooth_event_router.cc
index 27dec00..35bfd60 100644
--- a/extensions/browser/api/bluetooth/bluetooth_event_router.cc
+++ b/extensions/browser/api/bluetooth/bluetooth_event_router.cc
@@ -208,7 +208,7 @@
 
 BluetoothApiPairingDelegate* BluetoothEventRouter::GetPairingDelegate(
     const std::string& extension_id) {
-  return base::ContainsKey(pairing_delegate_map_, extension_id)
+  return base::Contains(pairing_delegate_map_, extension_id)
              ? pairing_delegate_map_[extension_id]
              : nullptr;
 }
@@ -250,7 +250,7 @@
     LOG(ERROR) << "Unable to get adapter for extension_id: " << extension_id;
     return;
   }
-  if (base::ContainsKey(pairing_delegate_map_, extension_id)) {
+  if (base::Contains(pairing_delegate_map_, extension_id)) {
     // For WebUI there may be more than one page open to the same url
     // (e.g. chrome://settings). These will share the same pairing delegate.
     BLUETOOTH_LOG(EVENT) << "Pairing delegate already exists for extension_id: "
@@ -267,7 +267,7 @@
 
 void BluetoothEventRouter::RemovePairingDelegate(
     const std::string& extension_id) {
-  if (base::ContainsKey(pairing_delegate_map_, extension_id)) {
+  if (base::Contains(pairing_delegate_map_, extension_id)) {
     BluetoothApiPairingDelegate* delegate = pairing_delegate_map_[extension_id];
     if (adapter_.get())
       adapter_->RemovePairingDelegate(delegate);
diff --git a/extensions/browser/api/declarative/declarative_rule_unittest.cc b/extensions/browser/api/declarative/declarative_rule_unittest.cc
index 87f7195..3d36fd7 100644
--- a/extensions/browser/api/declarative/declarative_rule_unittest.cc
+++ b/extensions/browser/api/declarative/declarative_rule_unittest.cc
@@ -119,7 +119,7 @@
 
   bool IsFulfilled(const MatchData& match_data) const {
     if (condition_set_id != -1 &&
-        !base::ContainsKey(match_data.url_matches, condition_set_id))
+        !base::Contains(match_data.url_matches, condition_set_id))
       return false;
     return match_data.value <= max_value;
   }
diff --git a/extensions/browser/api/declarative/deduping_factory.h b/extensions/browser/api/declarative/deduping_factory.h
index 542d8b1..cb96e0f 100644
--- a/extensions/browser/api/declarative/deduping_factory.h
+++ b/extensions/browser/api/declarative/deduping_factory.h
@@ -107,7 +107,7 @@
     const std::string& instance_type,
     typename DedupingFactory<BaseClassT>::Parameterized parameterized,
     FactoryMethod factory_method) {
-  DCHECK(!base::ContainsKey(factory_methods_, instance_type));
+  DCHECK(!base::Contains(factory_methods_, instance_type));
   factory_methods_[instance_type] = factory_method;
   if (parameterized == IS_PARAMETERIZED)
     parameterized_types_.insert(instance_type);
@@ -134,7 +134,7 @@
   // We can take a shortcut for objects that are not parameterized. For those
   // only a single instance may ever exist so we can simplify the creation
   // logic.
-  if (!base::ContainsKey(parameterized_types_, instance_type)) {
+  if (!base::Contains(parameterized_types_, instance_type)) {
     if (prototypes.empty()) {
       scoped_refptr<const BaseClassT> new_object =
           (*factory_method)(instance_type, value, error, bad_message);
diff --git a/extensions/browser/api/declarative/rules_registry.cc b/extensions/browser/api/declarative/rules_registry.cc
index 8b8367b..b85a68c 100644
--- a/extensions/browser/api/declarative/rules_registry.cc
+++ b/extensions/browser/api/declarative/rules_registry.cc
@@ -344,7 +344,7 @@
 void RulesRegistry::ProcessChangedRules(const std::string& extension_id) {
   DCHECK_CURRENTLY_ON(owner_thread());
 
-  DCHECK(base::ContainsKey(process_changed_rules_requested_, extension_id));
+  DCHECK(base::Contains(process_changed_rules_requested_, extension_id));
   process_changed_rules_requested_[extension_id] = NOT_SCHEDULED_FOR_PROCESSING;
 
   std::vector<const api::events::Rule*> new_rules;
diff --git a/extensions/browser/api/declarative_net_request/file_sequence_helper.cc b/extensions/browser/api/declarative_net_request/file_sequence_helper.cc
index aa55d5e3..5475c9e 100644
--- a/extensions/browser/api/declarative_net_request/file_sequence_helper.cc
+++ b/extensions/browser/api/declarative_net_request/file_sequence_helper.cc
@@ -144,7 +144,7 @@
 
   std::vector<dnr_api::Rule> result = std::move(current_rules);
   base::EraseIf(result, [&ids_to_remove](const dnr_api::Rule& rule) {
-    return base::ContainsKey(ids_to_remove, rule.id);
+    return base::Contains(ids_to_remove, rule.id);
   });
 
   return result;
diff --git a/extensions/browser/api/declarative_webrequest/webrequest_condition.cc b/extensions/browser/api/declarative_webrequest/webrequest_condition.cc
index 0b589d6..ce6e4819 100644
--- a/extensions/browser/api/declarative_webrequest/webrequest_condition.cc
+++ b/extensions/browser/api/declarative_webrequest/webrequest_condition.cc
@@ -98,12 +98,12 @@
 
   // Check URL attributes if present.
   if (url_matcher_conditions_.get() &&
-      !base::ContainsKey(request_data.url_match_ids,
-                         url_matcher_conditions_->id()))
+      !base::Contains(request_data.url_match_ids,
+                      url_matcher_conditions_->id()))
     return false;
   if (first_party_url_matcher_conditions_.get() &&
-      !base::ContainsKey(request_data.first_party_url_match_ids,
-                         first_party_url_matcher_conditions_->id()))
+      !base::Contains(request_data.first_party_url_match_ids,
+                      first_party_url_matcher_conditions_->id()))
     return false;
 
   // All condition attributes must be fulfilled for a fulfilled condition.
diff --git a/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute.cc b/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute.cc
index 0f263fa..77b6059 100644
--- a/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute.cc
+++ b/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute.cc
@@ -183,7 +183,7 @@
     const WebRequestData& request_data) const {
   if (!(request_data.stage & GetStages()))
     return false;
-  return base::ContainsValue(types_, request_data.request->web_request_type);
+  return base::Contains(types_, request_data.request->web_request_type);
 }
 
 WebRequestConditionAttribute::Type
@@ -266,9 +266,9 @@
       content_type, &mime_type, &charset, &had_charset, NULL);
 
   if (inclusive_) {
-    return base::ContainsValue(content_types_, mime_type);
+    return base::Contains(content_types_, mime_type);
   } else {
-    return !base::ContainsValue(content_types_, mime_type);
+    return !base::Contains(content_types_, mime_type);
   }
 }
 
diff --git a/extensions/browser/api/declarative_webrequest/webrequest_rules_registry.cc b/extensions/browser/api/declarative_webrequest/webrequest_rules_registry.cc
index 211d168b..bb551be 100644
--- a/extensions/browser/api/declarative_webrequest/webrequest_rules_registry.cc
+++ b/extensions/browser/api/declarative_webrequest/webrequest_rules_registry.cc
@@ -129,7 +129,7 @@
     if (!rule->tags().empty() && !ignore_tags[extension_id].empty()) {
       bool ignore_rule = false;
       for (const std::string& tag : rule->tags())
-        ignore_rule |= base::ContainsKey(ignore_tags[extension_id], tag);
+        ignore_rule |= base::Contains(ignore_tags[extension_id], tag);
       if (ignore_rule)
         continue;
     }
@@ -367,7 +367,7 @@
   for (const auto& url_match : url_matches) {
     auto rule_trigger = rule_triggers_.find(url_match);
     CHECK(rule_trigger != rule_triggers_.end());
-    if (!base::ContainsKey(*result, rule_trigger->second) &&
+    if (!base::Contains(*result, rule_trigger->second) &&
         rule_trigger->second->conditions().IsFulfilled(url_match, request_data))
       result->insert(rule_trigger->second);
   }
diff --git a/extensions/browser/api/document_scan/document_scan_api.cc b/extensions/browser/api/document_scan/document_scan_api.cc
index 3681514..d11321dc 100644
--- a/extensions/browser/api/document_scan/document_scan_api.cc
+++ b/extensions/browser/api/document_scan/document_scan_api.cc
@@ -48,7 +48,7 @@
   if (params_->options.mime_types) {
     std::vector<std::string>& mime_types = *params_->options.mime_types;
     for (; scanner_i != scanner_descriptions.end(); ++scanner_i) {
-      if (base::ContainsValue(mime_types, scanner_i->image_mime_type)) {
+      if (base::Contains(mime_types, scanner_i->image_mime_type)) {
         break;
       }
     }
diff --git a/extensions/browser/api/file_system/file_system_api.cc b/extensions/browser/api/file_system/file_system_api.cc
index 1ce53a3..176fedf 100644
--- a/extensions/browser/api/file_system/file_system_api.cc
+++ b/extensions/browser/api/file_system/file_system_api.cc
@@ -636,8 +636,7 @@
 
       // If we still need to find suggested_extension, hunt for it inside the
       // extensions returned from GetFileTypesFromAcceptOption.
-      if (need_suggestion &&
-          base::ContainsValue(extensions, suggested_extension)) {
+      if (need_suggestion && base::Contains(extensions, suggested_extension)) {
         need_suggestion = false;
       }
     }
diff --git a/extensions/browser/api/hid/hid_device_manager.cc b/extensions/browser/api/hid/hid_device_manager.cc
index a6d7c12..672d07c 100644
--- a/extensions/browser/api/hid/hid_device_manager.cc
+++ b/extensions/browser/api/hid/hid_device_manager.cc
@@ -227,7 +227,7 @@
   DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK_LT(next_resource_id_, std::numeric_limits<int>::max());
   int new_id = next_resource_id_++;
-  DCHECK(!base::ContainsKey(resource_ids_, device->guid));
+  DCHECK(!base::Contains(resource_ids_, device->guid));
   resource_ids_[device->guid] = new_id;
   devices_[new_id] = std::move(device);
 
diff --git a/extensions/browser/api/lock_screen_data/lock_screen_item_storage_unittest.cc b/extensions/browser/api/lock_screen_data/lock_screen_item_storage_unittest.cc
index f36bdce8..8417f22 100644
--- a/extensions/browser/api/lock_screen_data/lock_screen_item_storage_unittest.cc
+++ b/extensions/browser/api/lock_screen_data/lock_screen_item_storage_unittest.cc
@@ -704,7 +704,7 @@
       allowed_paths.push_back(
           test_dir_.GetPath().AppendASCII("deprecated_value_store"));
     }
-    EXPECT_TRUE(base::ContainsValue(allowed_paths, root))
+    EXPECT_TRUE(base::Contains(allowed_paths, root))
         << "Unexpected value store path " << root.value();
 
     return std::make_unique<LocalValueStoreCache>(
@@ -1328,13 +1328,13 @@
   EXPECT_TRUE(new_item);
 
   EXPECT_EQ(2u, items.size());
-  EXPECT_TRUE(base::ContainsValue(items, migrated_item_id));
-  EXPECT_TRUE(base::ContainsValue(items, initial_item_id));
+  EXPECT_TRUE(base::Contains(items, migrated_item_id));
+  EXPECT_TRUE(base::Contains(items, initial_item_id));
 
   GetAllItems(&items);
   ASSERT_EQ(2u, items.size());
-  EXPECT_TRUE(base::ContainsValue(items, migrated_item_id));
-  EXPECT_TRUE(base::ContainsValue(items, new_item->id()));
+  EXPECT_TRUE(base::Contains(items, migrated_item_id));
+  EXPECT_TRUE(base::Contains(items, new_item->id()));
 }
 
 TEST_F(LockScreenItemStorageTest,
diff --git a/extensions/browser/api/management/management_api.cc b/extensions/browser/api/management/management_api.cc
index e028420..0ff0806 100644
--- a/extensions/browser/api/management/management_api.cc
+++ b/extensions/browser/api/management/management_api.cc
@@ -733,7 +733,7 @@
       GetAvailableLaunchTypes(*extension, delegate);
 
   management::LaunchType app_launch_type = params->launch_type;
-  if (!base::ContainsValue(available_launch_types, app_launch_type)) {
+  if (!base::Contains(available_launch_types, app_launch_type)) {
     return RespondNow(Error(keys::kLaunchTypeNotAvailableError));
   }
 
diff --git a/extensions/browser/api/messaging/message_service.cc b/extensions/browser/api/messaging/message_service.cc
index 2793ff0..fdcf326e4 100644
--- a/extensions/browser/api/messaging/message_service.cc
+++ b/extensions/browser/api/messaging/message_service.cc
@@ -735,7 +735,7 @@
         PendingMessage(source_port_id, message));
     // A channel should only be holding pending messages because it is in one
     // of these states.
-    DCHECK(!base::ContainsKey(pending_lazy_context_channels_, channel_id));
+    DCHECK(!base::Contains(pending_lazy_context_channels_, channel_id));
     return;
   }
   EnqueuePendingMessageForLazyBackgroundLoad(source_port_id,
diff --git a/extensions/browser/api/networking_private/networking_private_chromeos.cc b/extensions/browser/api/networking_private/networking_private_chromeos.cc
index 9b89e85..a3f0864 100644
--- a/extensions/browser/api/networking_private/networking_private_chromeos.cc
+++ b/extensions/browser/api/networking_private/networking_private_chromeos.cc
@@ -682,7 +682,7 @@
       ::onc::network_type::kEthernet, ::onc::network_type::kWiFi,
       ::onc::network_type::kWimax, ::onc::network_type::kCellular};
   for (const char* technology : technology_types) {
-    if (base::ContainsKey(technologies_found, technology))
+    if (base::Contains(technologies_found, technology))
       continue;
     AppendDeviceState(technology, nullptr /* device */,
                       device_state_list.get());
diff --git a/extensions/browser/api/socket/udp_socket.cc b/extensions/browser/api/socket/udp_socket.cc
index eedd1713..7d63bee 100644
--- a/extensions/browser/api/socket/udp_socket.cc
+++ b/extensions/browser/api/socket/udp_socket.cc
@@ -323,7 +323,7 @@
   }
 
   std::string normalized_address = ip.ToString();
-  if (base::ContainsValue(multicast_groups_, normalized_address)) {
+  if (base::Contains(multicast_groups_, normalized_address)) {
     std::move(callback).Run(net::ERR_ADDRESS_INVALID);
     return;
   }
diff --git a/extensions/browser/api/storage/storage_api_unittest.cc b/extensions/browser/api/storage/storage_api_unittest.cc
index 2e056eb8b..561e1b4f 100644
--- a/extensions/browser/api/storage/storage_api_unittest.cc
+++ b/extensions/browser/api/storage/storage_api_unittest.cc
@@ -140,10 +140,10 @@
   RunSetFunction("key", "value");
   EXPECT_EQ(2u, event_observer.events().size());
 
-  EXPECT_TRUE(base::ContainsKey(event_observer.events(),
-                                api::storage::OnChanged::kEventName));
+  EXPECT_TRUE(base::Contains(event_observer.events(),
+                             api::storage::OnChanged::kEventName));
   EXPECT_TRUE(
-      base::ContainsKey(event_observer.events(), "storage.local.onChanged"));
+      base::Contains(event_observer.events(), "storage.local.onChanged"));
 }
 
 }  // namespace extensions
diff --git a/extensions/browser/api/system_display/system_display_apitest.cc b/extensions/browser/api/system_display/system_display_apitest.cc
index 446a499..a42934f 100644
--- a/extensions/browser/api/system_display/system_display_apitest.cc
+++ b/extensions/browser/api/system_display/system_display_apitest.cc
@@ -103,7 +103,7 @@
   }
 
   bool OverscanCalibrationStart(const std::string& id) override {
-    if (base::ContainsKey(overscan_started_, id))
+    if (base::Contains(overscan_started_, id))
       return false;
     overscan_started_.insert(id);
     return true;
@@ -112,21 +112,21 @@
   bool OverscanCalibrationAdjust(
       const std::string& id,
       const api::system_display::Insets& delta) override {
-    if (!base::ContainsKey(overscan_started_, id))
+    if (!base::Contains(overscan_started_, id))
       return false;
     overscan_adjusted_.insert(id);
     return true;
   }
 
   bool OverscanCalibrationReset(const std::string& id) override {
-    if (!base::ContainsKey(overscan_started_, id))
+    if (!base::Contains(overscan_started_, id))
       return false;
     overscan_adjusted_.erase(id);
     return true;
   }
 
   bool OverscanCalibrationComplete(const std::string& id) override {
-    if (!base::ContainsKey(overscan_started_, id))
+    if (!base::Contains(overscan_started_, id))
       return false;
     overscan_started_.erase(id);
     return true;
@@ -141,11 +141,11 @@
   bool unified_desktop_enabled() const { return unified_desktop_enabled_; }
 
   bool calibration_started(const std::string& id) const {
-    return base::ContainsKey(overscan_started_, id);
+    return base::Contains(overscan_started_, id);
   }
 
   bool calibration_changed(const std::string& id) const {
-    return base::ContainsKey(overscan_adjusted_, id);
+    return base::Contains(overscan_adjusted_, id);
   }
 
   const api::system_display::MirrorMode& mirror_mode() const {
diff --git a/extensions/browser/api/usb/usb_device_manager.cc b/extensions/browser/api/usb/usb_device_manager.cc
index 8a9112b..e65d7c8 100644
--- a/extensions/browser/api/usb/usb_device_manager.cc
+++ b/extensions/browser/api/usb/usb_device_manager.cc
@@ -225,7 +225,7 @@
     device::mojom::UsbDeviceInfoPtr device_info) {
   DCHECK(device_info);
   // Update the device list.
-  DCHECK(!base::ContainsKey(devices_, device_info->guid));
+  DCHECK(!base::Contains(devices_, device_info->guid));
   std::string guid = device_info->guid;
   auto result =
       devices_.insert(std::make_pair(std::move(guid), std::move(device_info)));
@@ -242,7 +242,7 @@
     device::mojom::UsbDeviceInfoPtr device_info) {
   DCHECK(device_info);
   // Update the device list.
-  DCHECK(base::ContainsKey(devices_, device_info->guid));
+  DCHECK(base::Contains(devices_, device_info->guid));
   devices_.erase(device_info->guid);
 
   DispatchEvent(usb::OnDeviceRemoved::kEventName, *device_info);
diff --git a/extensions/browser/api/vpn_provider/vpn_service.cc b/extensions/browser/api/vpn_provider/vpn_service.cc
index 722dfd40..d868923 100644
--- a/extensions/browser/api/vpn_provider/vpn_service.cc
+++ b/extensions/browser/api/vpn_provider/vpn_service.cc
@@ -368,7 +368,7 @@
   }
 
   const std::string key = GetKey(extension_id, configuration_name);
-  if (base::ContainsKey(key_to_configuration_map_, key)) {
+  if (base::Contains(key_to_configuration_map_, key)) {
     failure.Run(std::string(), std::string("Name not unique."));
     return;
   }
@@ -414,7 +414,7 @@
                                       const FailureCallback& failure) {
   // The ID is the configuration name for now. This may change in the future.
   const std::string key = GetKey(extension_id, configuration_id);
-  if (!base::ContainsKey(key_to_configuration_map_, key)) {
+  if (!base::Contains(key_to_configuration_map_, key)) {
     failure.Run(std::string(), std::string("Unauthorized access."));
     return;
   }
@@ -487,7 +487,7 @@
     const std::string& extension_id,
     const std::string& configuration_name) {
   const std::string key = GetKey(extension_id, configuration_name);
-  return base::ContainsKey(key_to_configuration_map_, key);
+  return base::Contains(key_to_configuration_map_, key);
 }
 
 bool VpnService::VerifyConfigIsConnectedForTesting(
@@ -635,7 +635,7 @@
         pepper_vpn_provider_proxy) {
   // The ID is the configuration name for now. This may change in the future.
   const std::string key = GetKey(extension_id, configuration_id);
-  if (!base::ContainsKey(key_to_configuration_map_, key)) {
+  if (!base::Contains(key_to_configuration_map_, key)) {
     failure.Run(std::string(),
                 std::string("Unauthorized access. "
                             "The configuration does not exist."));
diff --git a/extensions/browser/api/web_request/web_request_api.cc b/extensions/browser/api/web_request/web_request_api.cc
index 91b9768..e915e9fa 100644
--- a/extensions/browser/api/web_request/web_request_api.cc
+++ b/extensions/browser/api/web_request/web_request_api.cc
@@ -1963,7 +1963,7 @@
 
 bool ExtensionWebRequestEventRouter::HasAnyExtraHeadersListenerImpl(
     void* browser_context) {
-  return base::ContainsKey(extra_headers_listener_count_, browser_context);
+  return base::Contains(extra_headers_listener_count_, browser_context);
 }
 
 void ExtensionWebRequestEventRouter::UpdateExtraHeadersListenerOnUI(
@@ -2070,8 +2070,7 @@
     }
 
     const std::vector<WebRequestResourceType>& types = listener->filter.types;
-    if (!types.empty() &&
-        !base::ContainsValue(types, request->web_request_type)) {
+    if (!types.empty() && !base::Contains(types, request->web_request_type)) {
       continue;
     }
 
@@ -2337,7 +2336,7 @@
     Listeners& listeners = listeners_[browser_context][event_name];
 
     for (const auto& listener : listeners) {
-      if (!base::ContainsKey(listener->blocked_requests, request_id))
+      if (!base::Contains(listener->blocked_requests, request_id))
         continue;
       std::string delegate_info = l10n_util::GetStringFUTF8(
           IDS_LOAD_STATE_PARAMETER_EXTENSION,
diff --git a/extensions/browser/api/web_request/web_request_api_helpers.cc b/extensions/browser/api/web_request/web_request_api_helpers.cc
index 5b53610..1ea4fff2 100644
--- a/extensions/browser/api/web_request/web_request_api_helpers.cc
+++ b/extensions/browser/api/web_request/web_request_api_helpers.cc
@@ -949,7 +949,7 @@
         const std::string& value = modification.value();
 
         // We must not modify anything that has been deleted before.
-        if (base::ContainsKey(*removed_headers, key)) {
+        if (base::Contains(*removed_headers, key)) {
           extension_conflicts = true;
           break;
         }
@@ -970,7 +970,7 @@
 
         // We must not modify anything that has been set to a *different*
         // value before.
-        if (base::ContainsKey(*set_headers, key)) {
+        if (base::Contains(*set_headers, key)) {
           std::string current_value;
           if (!request_headers->GetHeader(key, &current_value) ||
               current_value != value) {
@@ -985,7 +985,7 @@
     // modified before.
     {
       for (const std::string& key : delta.deleted_request_headers) {
-        if (base::ContainsKey(*set_headers, base::ToLowerASCII(key))) {
+        if (base::Contains(*set_headers, base::ToLowerASCII(key))) {
           extension_conflicts = true;
           break;
         }
@@ -1002,7 +1002,7 @@
         std::string key = base::ToLowerASCII(modification.name());
         if (!request_headers->HasHeader(key)) {
           added_headers.insert(key);
-        } else if (!base::ContainsKey(added_headers, key)) {
+        } else if (!base::Contains(added_headers, key)) {
           // Note: |key| will only be present in |added_headers| if this is an
           // identical edit.
           overridden_headers.insert(key);
diff --git a/extensions/browser/app_window/app_window_geometry_cache.cc b/extensions/browser/app_window/app_window_geometry_cache.cc
index a17057e..57db19c9 100644
--- a/extensions/browser/app_window/app_window_geometry_cache.cc
+++ b/extensions/browser/app_window/app_window_geometry_cache.cc
@@ -56,7 +56,7 @@
   if (extension_data[window_id].bounds == bounds &&
       extension_data[window_id].window_state == window_state &&
       extension_data[window_id].screen_bounds == screen_bounds &&
-      !base::ContainsKey(unsynced_extensions_, extension_id))
+      !base::Contains(unsynced_extensions_, extension_id))
     return;
 
   base::Time now = base::Time::Now();
diff --git a/extensions/browser/app_window/app_window_registry.cc b/extensions/browser/app_window/app_window_registry.cc
index 4a59d05..cc11d60 100644
--- a/extensions/browser/app_window/app_window_registry.cc
+++ b/extensions/browser/app_window/app_window_registry.cc
@@ -182,7 +182,7 @@
 }
 
 void AppWindowRegistry::AddAppWindowToList(AppWindow* app_window) {
-  if (base::ContainsValue(app_windows_, app_window))
+  if (base::Contains(app_windows_, app_window))
     return;
   app_windows_.push_back(app_window);
 }
diff --git a/extensions/browser/content_hash_fetcher_unittest.cc b/extensions/browser/content_hash_fetcher_unittest.cc
index 02ec8aa..6e643d3 100644
--- a/extensions/browser/content_hash_fetcher_unittest.cc
+++ b/extensions/browser/content_hash_fetcher_unittest.cc
@@ -306,8 +306,7 @@
   ASSERT_NE(nullptr, result.get());
   EXPECT_TRUE(result->success);
   EXPECT_FALSE(result->was_cancelled);
-  EXPECT_TRUE(
-      base::ContainsKey(result->mismatch_paths, script_path.BaseName()));
+  EXPECT_TRUE(base::Contains(result->mismatch_paths, script_path.BaseName()));
 
   // Make sure the verified_contents.json file was written into the extension's
   // install dir.
diff --git a/extensions/browser/content_verifier.cc b/extensions/browser/content_verifier.cc
index 36fc369..52480ce 100644
--- a/extensions/browser/content_verifier.cc
+++ b/extensions/browser/content_verifier.cc
@@ -696,10 +696,10 @@
 
     // Background pages, scripts and content scripts should always be verified
     // regardless of their file type.
-    if (base::ContainsKey(background_or_content_paths, relative_unix_path))
+    if (base::Contains(background_or_content_paths, relative_unix_path))
       return true;
 
-    if (base::ContainsKey(browser_images, relative_unix_path))
+    if (base::Contains(browser_images, relative_unix_path))
       continue;
 
     base::FilePath full_path =
@@ -723,8 +723,8 @@
       // _locales/<some locale>/messages.json - if so then skip it.
       if (full_path.BaseName() == messages_file &&
           full_path.DirName().DirName() == locales_dir &&
-          base::ContainsKey(*all_locales,
-                            full_path.DirName().BaseName().MaybeAsASCII())) {
+          base::Contains(*all_locales,
+                         full_path.DirName().BaseName().MaybeAsASCII())) {
         continue;
       }
     }
diff --git a/extensions/browser/event_listener_map.cc b/extensions/browser/event_listener_map.cc
index d7d18d5..29a399e 100644
--- a/extensions/browser/event_listener_map.cc
+++ b/extensions/browser/event_listener_map.cc
@@ -346,7 +346,7 @@
 }
 
 bool EventListenerMap::IsFilteredEvent(const Event& event) const {
-  return base::ContainsKey(filtered_events_, event.event_name);
+  return base::Contains(filtered_events_, event.event_name);
 }
 
 }  // namespace extensions
diff --git a/extensions/browser/events/lazy_event_dispatcher.cc b/extensions/browser/events/lazy_event_dispatcher.cc
index fc3aa96..fe30c55 100644
--- a/extensions/browser/events/lazy_event_dispatcher.cc
+++ b/extensions/browser/events/lazy_event_dispatcher.cc
@@ -53,7 +53,7 @@
 
 bool LazyEventDispatcher::HasAlreadyDispatched(
     const LazyContextId& dispatch_context) const {
-  return base::ContainsKey(dispatched_ids_, dispatch_context);
+  return base::Contains(dispatched_ids_, dispatch_context);
 }
 
 bool LazyEventDispatcher::QueueEventDispatch(
diff --git a/extensions/browser/extension_registrar.cc b/extensions/browser/extension_registrar.cc
index ea8e8b37..3c54b852 100644
--- a/extensions/browser/extension_registrar.cc
+++ b/extensions/browser/extension_registrar.cc
@@ -355,7 +355,7 @@
   // even if it's not permanently installed.
   unloaded_extension_paths_[extension->id()] = extension->path();
 
-  DCHECK(!base::ContainsKey(reloading_extensions_, extension->id()))
+  DCHECK(!base::Contains(reloading_extensions_, extension->id()))
       << "Enabled extension shouldn't be marked for reloading";
 
   registry_->AddTerminated(extension);
@@ -513,7 +513,7 @@
   // For orphaned devtools, we will reconnect devtools to it later in
   // DidCreateRenderViewForBackgroundPage().
   bool has_orphaned_dev_tools =
-      base::ContainsKey(orphaned_dev_tools_, extension->id());
+      base::Contains(orphaned_dev_tools_, extension->id());
 
   // Reloading component extension does not trigger install, so RuntimeAPI won't
   // be able to detect its loading. Therefore, we need to spin up its lazy
diff --git a/extensions/browser/guest_view/mime_handler_view/mime_handler_view_attach_helper.cc b/extensions/browser/guest_view/mime_handler_view/mime_handler_view_attach_helper.cc
index 8a68ece..ad188c0 100644
--- a/extensions/browser/guest_view/mime_handler_view/mime_handler_view_attach_helper.cc
+++ b/extensions/browser/guest_view/mime_handler_view/mime_handler_view_attach_helper.cc
@@ -74,7 +74,7 @@
     int32_t render_process_id) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   auto& map = *GetProcessIdToHelperMap();
-  if (!base::ContainsKey(map, render_process_id)) {
+  if (!base::Contains(map, render_process_id)) {
     auto* process_host = content::RenderProcessHost::FromID(render_process_id);
     if (!process_host)
       return nullptr;
diff --git a/extensions/browser/guest_view/mime_handler_view/mime_handler_view_embedder.cc b/extensions/browser/guest_view/mime_handler_view/mime_handler_view_embedder.cc
index 31f32ff6..1a9beda 100644
--- a/extensions/browser/guest_view/mime_handler_view/mime_handler_view_embedder.cc
+++ b/extensions/browser/guest_view/mime_handler_view/mime_handler_view_embedder.cc
@@ -50,8 +50,8 @@
                                      const std::string& mime_type,
                                      const std::string& stream_id,
                                      const std::string& internal_id) {
-  DCHECK(!base::ContainsKey(*GetMimeHandlerViewEmbeddersMap(),
-                            frame_tree_node_id));
+  DCHECK(
+      !base::Contains(*GetMimeHandlerViewEmbeddersMap(), frame_tree_node_id));
   GetMimeHandlerViewEmbeddersMap()->insert_or_assign(
       frame_tree_node_id, base::WrapUnique(new MimeHandlerViewEmbedder(
                               frame_tree_node_id, resource_url, mime_type,
diff --git a/extensions/browser/image_sanitizer_unittest.cc b/extensions/browser/image_sanitizer_unittest.cc
index 44e311f..a14e2b6 100644
--- a/extensions/browser/image_sanitizer_unittest.cc
+++ b/extensions/browser/image_sanitizer_unittest.cc
@@ -268,7 +268,7 @@
     EXPECT_TRUE(base::GetFileSize(full_path, &file_size));
     EXPECT_GT(file_size, 0);
 
-    ASSERT_TRUE(base::ContainsKey(*decoded_images(), path));
+    ASSERT_TRUE(base::Contains(*decoded_images(), path));
     EXPECT_FALSE((*decoded_images())[path].drawsNothing());
   }
   // No extra images should have been reported.
diff --git a/extensions/browser/lazy_background_task_queue.cc b/extensions/browser/lazy_background_task_queue.cc
index c0a5fe3..523134f 100644
--- a/extensions/browser/lazy_background_task_queue.cc
+++ b/extensions/browser/lazy_background_task_queue.cc
@@ -233,7 +233,7 @@
     content::BrowserContext* browser_context,
     const Extension* extension) {
   PendingTasksKey key(browser_context, extension->id());
-  if (!base::ContainsKey(pending_tasks_, key))
+  if (!base::Contains(pending_tasks_, key))
     return;
 
   ProcessManager* pm = ProcessManager::Get(browser_context);
diff --git a/extensions/browser/renderer_startup_helper.cc b/extensions/browser/renderer_startup_helper.cc
index a930cfa..1e368fe 100644
--- a/extensions/browser/renderer_startup_helper.cc
+++ b/extensions/browser/renderer_startup_helper.cc
@@ -151,8 +151,8 @@
       ExtensionRegistry::Get(browser_context_)->enabled_extensions();
   for (const auto& ext : extensions) {
     // OnLoadedExtension should have already been called for the extension.
-    DCHECK(base::ContainsKey(extension_process_map_, ext->id()));
-    DCHECK(!base::ContainsKey(extension_process_map_[ext->id()], process));
+    DCHECK(base::Contains(extension_process_map_, ext->id()));
+    DCHECK(!base::Contains(extension_process_map_[ext->id()], process));
 
     if (!IsExtensionVisibleToContext(*ext, renderer_context))
       continue;
@@ -174,8 +174,8 @@
     for (const ExtensionId& id : iter->second) {
       // The extension should be loaded in the process.
       DCHECK(extensions.Contains(id));
-      DCHECK(base::ContainsKey(extension_process_map_, id));
-      DCHECK(base::ContainsKey(extension_process_map_[id], process));
+      DCHECK(base::Contains(extension_process_map_, id));
+      DCHECK(base::Contains(extension_process_map_[id], process));
       process->Send(new ExtensionMsg_ActivateExtension(id));
     }
   }
@@ -202,7 +202,7 @@
     content::RenderProcessHost* process) {
   // The extension should have been loaded already. Dump without crashing to
   // debug crbug.com/528026.
-  if (!base::ContainsKey(extension_process_map_, extension.id())) {
+  if (!base::Contains(extension_process_map_, extension.id())) {
 #if DCHECK_IS_ON()
     NOTREACHED() << "Extension " << extension.id()
                  << "activated before loading";
@@ -215,8 +215,8 @@
   if (!IsExtensionVisibleToContext(extension, process->GetBrowserContext()))
     return;
 
-  if (base::ContainsKey(initialized_processes_, process)) {
-    DCHECK(base::ContainsKey(extension_process_map_[extension.id()], process));
+  if (base::Contains(initialized_processes_, process)) {
+    DCHECK(base::Contains(extension_process_map_[extension.id()], process));
     process->Send(new ExtensionMsg_ActivateExtension(extension.id()));
   } else {
     pending_active_extensions_[process].insert(extension.id());
@@ -227,7 +227,7 @@
   // Extension was already loaded.
   // TODO(crbug.com/708230): Ensure that clients don't call this for an
   // already loaded extension and change this to a DCHECK.
-  if (base::ContainsKey(extension_process_map_, extension.id()))
+  if (base::Contains(extension_process_map_, extension.id()))
     return;
 
   // Mark the extension as loaded.
@@ -274,13 +274,13 @@
   // Extension is not loaded.
   // TODO(crbug.com/708230): Ensure that clients call this for a loaded
   // extension only and change this to a DCHECK.
-  if (!base::ContainsKey(extension_process_map_, extension.id()))
+  if (!base::Contains(extension_process_map_, extension.id()))
     return;
 
   const std::set<content::RenderProcessHost*>& loaded_process_set =
       extension_process_map_[extension.id()];
   for (content::RenderProcessHost* process : loaded_process_set) {
-    DCHECK(base::ContainsKey(initialized_processes_, process));
+    DCHECK(base::Contains(initialized_processes_, process));
     process->Send(new ExtensionMsg_Unloaded(extension.id()));
   }
 
diff --git a/extensions/browser/renderer_startup_helper_unittest.cc b/extensions/browser/renderer_startup_helper_unittest.cc
index 7afa40f..bcc5f85 100644
--- a/extensions/browser/renderer_startup_helper_unittest.cc
+++ b/extensions/browser/renderer_startup_helper_unittest.cc
@@ -110,25 +110,24 @@
   }
 
   bool IsProcessInitialized(content::RenderProcessHost* rph) {
-    return base::ContainsKey(helper_->initialized_processes_, rph);
+    return base::Contains(helper_->initialized_processes_, rph);
   }
 
   bool IsExtensionLoaded(const Extension& extension) {
-    return base::ContainsKey(helper_->extension_process_map_, extension.id());
+    return base::Contains(helper_->extension_process_map_, extension.id());
   }
 
   bool IsExtensionLoadedInProcess(const Extension& extension,
                                   content::RenderProcessHost* rph) {
     return IsExtensionLoaded(extension) &&
-           base::ContainsKey(helper_->extension_process_map_[extension.id()],
-                             rph);
+           base::Contains(helper_->extension_process_map_[extension.id()], rph);
   }
 
   bool IsExtensionPendingActivationInProcess(const Extension& extension,
                                              content::RenderProcessHost* rph) {
-    return base::ContainsKey(helper_->pending_active_extensions_, rph) &&
-           base::ContainsKey(helper_->pending_active_extensions_[rph],
-                             extension.id());
+    return base::Contains(helper_->pending_active_extensions_, rph) &&
+           base::Contains(helper_->pending_active_extensions_[rph],
+                          extension.id());
   }
 
   std::unique_ptr<RendererStartupHelper> helper_;
diff --git a/extensions/browser/service_worker_task_queue.cc b/extensions/browser/service_worker_task_queue.cc
index a81018a..27e1a76 100644
--- a/extensions/browser/service_worker_task_queue.cc
+++ b/extensions/browser/service_worker_task_queue.cc
@@ -324,8 +324,8 @@
     int64_t version_id,
     int process_id,
     int thread_id) {
-  if (!base::ContainsKey(loaded_workers_, context_id) ||
-      !base::ContainsKey(started_workers_, context_id)) {
+  if (!base::Contains(loaded_workers_, context_id) ||
+      !base::Contains(started_workers_, context_id)) {
     // The worker hasn't finished starting (DidStartWorkerForScope) or hasn't
     // finished loading (DidStartServiceWorkerContext).
     // Wait for the next event, and run the tasks then.
diff --git a/extensions/browser/url_loader_factory_manager.cc b/extensions/browser/url_loader_factory_manager.cc
index 7da47628..1373f0a 100644
--- a/extensions/browser/url_loader_factory_manager.cc
+++ b/extensions/browser/url_loader_factory_manager.cc
@@ -317,7 +317,7 @@
 
     const std::string& hash = extension.hashed_id().value();
     DCHECK(IsValidHashedExtensionId(hash));
-    return base::ContainsKey(GetExtensionsAllowlist(), hash);
+    return base::Contains(GetExtensionsAllowlist(), hash);
   }
 
   // Safe fallback for future extension manifest versions.
@@ -435,7 +435,7 @@
           content::WebContents::FromRenderFrameHost(found_frame)->GetOpener();
     }
     if (!next_candidate ||
-        base::ContainsKey(already_visited_frames, next_candidate)) {
+        base::Contains(already_visited_frames, next_candidate)) {
       break;
     }
 
diff --git a/extensions/browser/verified_contents.cc b/extensions/browser/verified_contents.cc
index 9d8032b..489ec82 100644
--- a/extensions/browser/verified_contents.cc
+++ b/extensions/browser/verified_contents.cc
@@ -246,13 +246,13 @@
     const base::FilePath& relative_path) const {
   base::FilePath::StringType path = base::ToLowerASCII(
       relative_path.NormalizePathSeparatorsTo('/').value());
-  if (base::ContainsKey(root_hashes_, path))
+  if (base::Contains(root_hashes_, path))
     return true;
 
 #if defined(OS_WIN)
   base::FilePath::StringType trimmed_path;
   if (TrimDotSpaceSuffix(path, &trimmed_path))
-    return base::ContainsKey(root_hashes_, trimmed_path);
+    return base::Contains(root_hashes_, trimmed_path);
 #endif  // defined(OS_WIN)
   return false;
 }
diff --git a/extensions/browser/zipfile_installer.cc b/extensions/browser/zipfile_installer.cc
index 3fc0f68..b748b7a 100644
--- a/extensions/browser/zipfile_installer.cc
+++ b/extensions/browser/zipfile_installer.cc
@@ -203,7 +203,7 @@
     // Allow filenames with no extension.
     if (extension.empty())
       return true;
-    return base::ContainsValue(kAllowedThemeFiletypes, extension);
+    return base::Contains(kAllowedThemeFiletypes, extension);
   }
   return !base::FilePath::CompareEqualIgnoreCase(file_path.FinalExtension(),
                                                  FILE_PATH_LITERAL(".exe"));
diff --git a/extensions/common/extension_l10n_util.cc b/extensions/common/extension_l10n_util.cc
index ee2fa5b..0f6a3ed 100644
--- a/extensions/common/extension_l10n_util.cc
+++ b/extensions/common/extension_l10n_util.cc
@@ -472,7 +472,7 @@
   if (subdir.empty())
     return true;  // Non-ASCII.
 
-  if (base::ContainsValue(subdir, '.'))
+  if (base::Contains(subdir, '.'))
     return true;
 
   if (all_locales.find(subdir) == all_locales.end())
diff --git a/extensions/common/extension_set.cc b/extensions/common/extension_set.cc
index 217c2df..070acdf 100644
--- a/extensions/common/extension_set.cc
+++ b/extensions/common/extension_set.cc
@@ -63,7 +63,7 @@
 }
 
 bool ExtensionSet::Insert(const scoped_refptr<const Extension>& extension) {
-  bool was_present = base::ContainsKey(extensions_, extension->id());
+  bool was_present = base::Contains(extensions_, extension->id());
   extensions_[extension->id()] = extension;
   return !was_present;
 }
diff --git a/extensions/common/features/simple_feature.cc b/extensions/common/features/simple_feature.cc
index 986a631..ee03ee41 100644
--- a/extensions/common/features/simple_feature.cc
+++ b/extensions/common/features/simple_feature.cc
@@ -431,7 +431,7 @@
   if (!IsValidHashedExtensionId(hashed_id))
     return false;
 
-  return base::ContainsValue(list, hashed_id.value());
+  return base::Contains(list, hashed_id.value());
 }
 
 bool SimpleFeature::MatchesManifestLocation(
@@ -454,14 +454,14 @@
   if (session_types_.empty())
     return true;
 
-  if (base::ContainsValue(session_types_, session_type))
+  if (base::Contains(session_types_, session_type))
     return true;
 
   // AUTOLAUNCHED_KIOSK session type is subset of KIOSK - accept auto-lauched
   // kiosk session if kiosk session is allowed. This is the only exception to
   // rejecting session type that is not present in |session_types_|
   return session_type == FeatureSessionType::AUTOLAUNCHED_KIOSK &&
-         base::ContainsValue(session_types_, FeatureSessionType::KIOSK);
+         base::Contains(session_types_, FeatureSessionType::KIOSK);
 }
 
 Feature::Availability SimpleFeature::CheckDependencies(
@@ -545,7 +545,7 @@
     version_info::Channel channel,
     FeatureSessionType session_type) const {
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
-  if (!platforms_.empty() && !base::ContainsValue(platforms_, platform))
+  if (!platforms_.empty() && !base::Contains(platforms_, platform))
     return CreateAvailability(INVALID_PLATFORM);
 
   if (channel_ && *channel_ < GetCurrentChannel()) {
@@ -582,7 +582,7 @@
   Manifest::Type type_to_check =
       (type == Manifest::TYPE_USER_SCRIPT) ? Manifest::TYPE_EXTENSION : type;
   if (!extension_types_.empty() &&
-      !base::ContainsValue(extension_types_, type_to_check)) {
+      !base::Contains(extension_types_, type_to_check)) {
     return CreateAvailability(INVALID_TYPE, type);
   }
 
@@ -622,7 +622,7 @@
   // extension API calls, since there's no guarantee that the extension is
   // "active" in current renderer process when the API permission check is
   // done.
-  if (!contexts_.empty() && !base::ContainsValue(contexts_, context))
+  if (!contexts_.empty() && !base::Contains(contexts_, context))
     return CreateAvailability(INVALID_CONTEXT, context);
 
   // TODO(kalman): Consider checking |matches_| regardless of context type.
diff --git a/extensions/common/manifest_handler.cc b/extensions/common/manifest_handler.cc
index 73bd75c..e0ba65d2 100644
--- a/extensions/common/manifest_handler.cc
+++ b/extensions/common/manifest_handler.cc
@@ -236,7 +236,7 @@
         CHECK(prereq_iter != handlers_.end())
             << "Extension manifest handler depends on unrecognized key " << key;
         // Prerequisite is in our map.
-        if (base::ContainsKey(priority_map_, prereq_iter->second))
+        if (base::Contains(priority_map_, prereq_iter->second))
           unsatisfied--;
       }
       if (unsatisfied == 0) {
diff --git a/extensions/common/manifest_handlers/permissions_parser.cc b/extensions/common/manifest_handlers/permissions_parser.cc
index c7821d9..fb3fdaff 100644
--- a/extensions/common/manifest_handlers/permissions_parser.cc
+++ b/extensions/common/manifest_handlers/permissions_parser.cc
@@ -123,7 +123,7 @@
 
   // Users should be able to enable file access for extensions with activeTab.
   if (!can_execute_script_everywhere &&
-      base::ContainsKey(api_permissions, APIPermission::kActiveTab)) {
+      base::Contains(api_permissions, APIPermission::kActiveTab)) {
     extension->set_wants_file_access(true);
   }
 
diff --git a/extensions/common/message_bundle.cc b/extensions/common/message_bundle.cc
index f870167..aeb728d 100644
--- a/extensions/common/message_bundle.cc
+++ b/extensions/common/message_bundle.cc
@@ -113,7 +113,7 @@
   // Add all reserved messages to the dictionary, but check for collisions.
   auto it = append_messages.begin();
   for (; it != append_messages.end(); ++it) {
-    if (base::ContainsKey(dictionary_, it->first)) {
+    if (base::Contains(dictionary_, it->first)) {
       *error = ErrorUtils::FormatErrorMessage(
           errors::kReservedMessageFound, it->first);
       return false;
diff --git a/extensions/common/permissions/api_permission_set.cc b/extensions/common/permissions/api_permission_set.cc
index fd2ae552..b4fedf6 100644
--- a/extensions/common/permissions/api_permission_set.cc
+++ b/extensions/common/permissions/api_permission_set.cc
@@ -306,7 +306,7 @@
     const std::set<APIPermission::ID>& permission_ids) const {
   PermissionIDSet subset;
   for (const auto& permission : permissions_) {
-    if (base::ContainsKey(permission_ids, permission.id())) {
+    if (base::Contains(permission_ids, permission.id())) {
       subset.permissions_.insert(permission);
     }
   }
diff --git a/extensions/common/permissions/permissions_data.cc b/extensions/common/permissions/permissions_data.cc
index b09be07..fe5e92c 100644
--- a/extensions/common/permissions/permissions_data.cc
+++ b/extensions/common/permissions/permissions_data.cc
@@ -83,7 +83,7 @@
   const ExtensionsClient::ScriptingWhitelist& whitelist =
       ExtensionsClient::Get()->GetScriptingWhitelist();
 
-  return base::ContainsValue(whitelist, extension_id);
+  return base::Contains(whitelist, extension_id);
 }
 
 bool PermissionsData::IsRestrictedUrl(const GURL& document_url,
diff --git a/extensions/common/permissions/permissions_info.cc b/extensions/common/permissions/permissions_info.cc
index 008cd71..1e0e8dbf 100644
--- a/extensions/common/permissions/permissions_info.cc
+++ b/extensions/common/permissions/permissions_info.cc
@@ -74,15 +74,15 @@
 }
 
 void PermissionsInfo::RegisterAlias(const Alias& alias) {
-  DCHECK(base::ContainsKey(name_map_, alias.real_name));
-  DCHECK(!base::ContainsKey(name_map_, alias.name));
+  DCHECK(base::Contains(name_map_, alias.real_name));
+  DCHECK(!base::Contains(name_map_, alias.name));
   name_map_[alias.name] = name_map_[alias.real_name];
 }
 
 void PermissionsInfo::RegisterPermission(
     std::unique_ptr<APIPermissionInfo> permission) {
-  DCHECK(!base::ContainsKey(id_map_, permission->id()));
-  DCHECK(!base::ContainsKey(name_map_, permission->name()));
+  DCHECK(!base::Contains(id_map_, permission->id()));
+  DCHECK(!base::Contains(name_map_, permission->name()));
 
   name_map_[permission->name()] = permission.get();
   id_map_[permission->id()] = std::move(permission);
diff --git a/extensions/common/url_pattern_set_unittest.cc b/extensions/common/url_pattern_set_unittest.cc
index 88e2972..852b1b2ca 100644
--- a/extensions/common/url_pattern_set_unittest.cc
+++ b/extensions/common/url_pattern_set_unittest.cc
@@ -565,8 +565,8 @@
 
   EXPECT_EQ(2UL, string_vector->size());
 
-  EXPECT_TRUE(base::ContainsValue(*string_vector, "https://google.com/"));
-  EXPECT_TRUE(base::ContainsValue(*string_vector, "https://yahoo.com/"));
+  EXPECT_TRUE(base::Contains(*string_vector, "https://google.com/"));
+  EXPECT_TRUE(base::Contains(*string_vector, "https://yahoo.com/"));
 }
 
 }  // namespace extensions
diff --git a/extensions/renderer/api/automation/automation_ax_tree_wrapper.cc b/extensions/renderer/api/automation/automation_ax_tree_wrapper.cc
index 8f1c1a6a..843bac9 100644
--- a/extensions/renderer/api/automation/automation_ax_tree_wrapper.cc
+++ b/extensions/renderer/api/automation/automation_ax_tree_wrapper.cc
@@ -297,7 +297,7 @@
 
   // Refresh child tree id  mappings.
   for (const ui::AXTreeID& tree_id : tree_.GetAllChildTreeIds()) {
-    DCHECK(!base::ContainsKey(child_tree_id_reverse_map, tree_id));
+    DCHECK(!base::Contains(child_tree_id_reverse_map, tree_id));
     child_tree_id_reverse_map.insert(std::make_pair(tree_id, this));
   }
 
diff --git a/extensions/renderer/bindings/api_bindings_system_unittest.cc b/extensions/renderer/bindings/api_bindings_system_unittest.cc
index 0df93d7..fc8b5ea 100644
--- a/extensions/renderer/bindings/api_bindings_system_unittest.cc
+++ b/extensions/renderer/bindings/api_bindings_system_unittest.cc
@@ -159,7 +159,7 @@
 
 const base::DictionaryValue& APIBindingsSystemTest::GetAPISchema(
     const std::string& api_name) {
-  EXPECT_TRUE(base::ContainsKey(api_schemas_, api_name));
+  EXPECT_TRUE(base::Contains(api_schemas_, api_name));
   return *api_schemas_[api_name];
 }
 
diff --git a/extensions/renderer/feature_cache_unittest.cc b/extensions/renderer/feature_cache_unittest.cc
index b36e57ce..afb9a55 100644
--- a/extensions/renderer/feature_cache_unittest.cc
+++ b/extensions/renderer/feature_cache_unittest.cc
@@ -34,7 +34,7 @@
 bool HasFeature(FeatureCache& cache,
                 const FakeContext& context,
                 const std::string& feature) {
-  return base::ContainsValue(
+  return base::Contains(
       cache.GetAvailableFeatures(context.context_type, context.extension,
                                  context.url),
       feature);
diff --git a/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container_manager.cc b/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container_manager.cc
index de8c3c7..04e6e52d 100644
--- a/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container_manager.cc
+++ b/extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container_manager.cc
@@ -62,7 +62,7 @@
   }
   int32_t routing_id = render_frame->GetRoutingID();
   auto& map = *GetRenderFrameMap();
-  if (base::ContainsKey(map, routing_id))
+  if (base::Contains(map, routing_id))
     return map[routing_id].get();
   if (create_if_does_not_exits) {
     map[routing_id] =
diff --git a/extensions/renderer/native_renderer_messaging_service.cc b/extensions/renderer/native_renderer_messaging_service.cc
index e186dfa..3906d5e 100644
--- a/extensions/renderer/native_renderer_messaging_service.cc
+++ b/extensions/renderer/native_renderer_messaging_service.cc
@@ -352,7 +352,7 @@
   v8::HandleScope handle_scope(script_context->isolate());
   MessagingPerContextData* data = GetPerContextData<MessagingPerContextData>(
       script_context->v8_context(), kDontCreateIfMissing);
-  return data && base::ContainsKey(data->ports, port_id);
+  return data && base::Contains(data->ports, port_id);
 }
 
 void NativeRendererMessagingService::DispatchOnConnectToListeners(
@@ -530,7 +530,7 @@
   MessagingPerContextData* data =
       GetPerContextData<MessagingPerContextData>(context, kCreateIfMissing);
   DCHECK(data);
-  DCHECK(!base::ContainsKey(data->ports, port_id));
+  DCHECK(!base::Contains(data->ports, port_id));
 
   gin::Handle<GinPort> port_handle = gin::CreateHandle(
       isolate,
@@ -553,7 +553,7 @@
   MessagingPerContextData* data = GetPerContextData<MessagingPerContextData>(
       script_context->v8_context(), kDontCreateIfMissing);
   DCHECK(data);
-  DCHECK(base::ContainsKey(data->ports, port_id));
+  DCHECK(base::Contains(data->ports, port_id));
 
   GinPort* port = nullptr;
   gin::Converter<GinPort*>::FromV8(context->GetIsolate(),
diff --git a/extensions/renderer/one_time_message_handler.cc b/extensions/renderer/one_time_message_handler.cc
index 0904bbb..ee5a652 100644
--- a/extensions/renderer/one_time_message_handler.cc
+++ b/extensions/renderer/one_time_message_handler.cc
@@ -155,8 +155,8 @@
                                                    kDontCreateIfMissing);
   if (!data)
     return false;
-  return port_id.is_opener ? base::ContainsKey(data->openers, port_id)
-                           : base::ContainsKey(data->receivers, port_id);
+  return port_id.is_opener ? base::Contains(data->openers, port_id)
+                           : base::Contains(data->receivers, port_id);
 }
 
 void OneTimeMessageHandler::SendMessage(
@@ -220,7 +220,7 @@
   OneTimeMessageContextData* data =
       GetPerContextData<OneTimeMessageContextData>(context, kCreateIfMissing);
   DCHECK(data);
-  DCHECK(!base::ContainsKey(data->receivers, target_port_id));
+  DCHECK(!base::Contains(data->receivers, target_port_id));
   OneTimeReceiver& receiver = data->receivers[target_port_id];
   receiver.sender.Reset(isolate, sender);
   receiver.routing_id = RoutingIdForScriptContext(script_context);
diff --git a/extensions/renderer/resource_bundle_source_map.cc b/extensions/renderer/resource_bundle_source_map.cc
index 22dda472..f087859 100644
--- a/extensions/renderer/resource_bundle_source_map.cc
+++ b/extensions/renderer/resource_bundle_source_map.cc
@@ -89,7 +89,7 @@
 }
 
 bool ResourceBundleSourceMap::Contains(const std::string& name) const {
-  return base::ContainsKey(resource_map_, name);
+  return base::Contains(resource_map_, name);
 }
 
 }  // namespace extensions
diff --git a/extensions/renderer/script_context.cc b/extensions/renderer/script_context.cc
index 0bfe1ac4..cfb1d90 100644
--- a/extensions/renderer/script_context.cc
+++ b/extensions/renderer/script_context.cc
@@ -362,7 +362,7 @@
 
     // Avoid an infinite loop - see https://crbug.com/568432 and
     // https://crbug.com/883526.
-    if (base::ContainsKey(already_visited_frames, parent))
+    if (base::Contains(already_visited_frames, parent))
       return document_url;
 
     parent_document = parent && parent->IsWebLocalFrame()
diff --git a/gpu/ipc/service/gpu_watchdog_thread.cc b/gpu/ipc/service/gpu_watchdog_thread.cc
index 857e92b..b51f7ac2 100644
--- a/gpu/ipc/service/gpu_watchdog_thread.cc
+++ b/gpu/ipc/service/gpu_watchdog_thread.cc
@@ -479,8 +479,7 @@
   crash_keys::available_physical_memory_in_mb.Set(
       base::NumberToString(available_physical_memory));
 
-  ui::gl::ShaderTracking* shader_tracking =
-      ui::gl::ShaderTracking::GetInstance();
+  gl::ShaderTracking* shader_tracking = gl::ShaderTracking::GetInstance();
   if (shader_tracking) {
     std::string shaders[2];
     shader_tracking->GetShaders(shaders, shaders + 1);
diff --git a/ios/chrome/browser/app_startup_parameters.mm b/ios/chrome/browser/app_startup_parameters.mm
index e9ccd89..11ba0724 100644
--- a/ios/chrome/browser/app_startup_parameters.mm
+++ b/ios/chrome/browser/app_startup_parameters.mm
@@ -61,8 +61,8 @@
     }
 
     // Currently only Payment Request parameters are supported.
-    if (base::ContainsKey(parameters, payments::kPaymentRequestIDExternal) &&
-        base::ContainsKey(parameters, payments::kPaymentRequestDataExternal)) {
+    if (base::Contains(parameters, payments::kPaymentRequestIDExternal) &&
+        base::Contains(parameters, payments::kPaymentRequestDataExternal)) {
       _externalURLParams = parameters;
       _completePaymentRequest = YES;
     }
diff --git a/ios/chrome/browser/autofill/form_structure_browsertest.mm b/ios/chrome/browser/autofill/form_structure_browsertest.mm
index 575501b..5b1d2f3f 100644
--- a/ios/chrome/browser/autofill/form_structure_browsertest.mm
+++ b/ios/chrome/browser/autofill/form_structure_browsertest.mm
@@ -242,7 +242,7 @@
 // DISABLED_DataDrivenHeuristics.
 TEST_P(FormStructureBrowserTest, DataDrivenHeuristics) {
   bool is_expected_to_pass =
-      !base::ContainsKey(GetFailingTestNames(), GetParam().BaseName().value());
+      !base::Contains(GetFailingTestNames(), GetParam().BaseName().value());
   RunOneDataDrivenTest(GetParam(), GetIOSOutputDirectory(),
                        is_expected_to_pass);
 }
diff --git a/ios/chrome/browser/bookmarks/bookmarks_utils.cc b/ios/chrome/browser/bookmarks/bookmarks_utils.cc
index f3bb3f003..62d97f9 100644
--- a/ios/chrome/browser/bookmarks/bookmarks_utils.cc
+++ b/ios/chrome/browser/bookmarks/bookmarks_utils.cc
@@ -72,7 +72,7 @@
 
 bool IsPrimaryPermanentNode(const BookmarkNode* node, BookmarkModel* model) {
   std::vector<const BookmarkNode*> primary_nodes(PrimaryPermanentNodes(model));
-  return base::ContainsValue(primary_nodes, node);
+  return base::Contains(primary_nodes, node);
 }
 
 const BookmarkNode* RootLevelFolderForNode(const BookmarkNode* node,
@@ -84,7 +84,7 @@
 
   const std::vector<const BookmarkNode*> root_folders(RootLevelFolders(model));
   const BookmarkNode* top = node;
-  while (top && !base::ContainsValue(root_folders, top)) {
+  while (top && !base::Contains(root_folders, top)) {
     top = top->parent();
   }
   return top;
diff --git a/ios/chrome/browser/browser_state/chrome_browser_state_io_data.mm b/ios/chrome/browser/browser_state/chrome_browser_state_io_data.mm
index d74d3ae..e472186c 100644
--- a/ios/chrome/browser/browser_state/chrome_browser_state_io_data.mm
+++ b/ios/chrome/browser/browser_state/chrome_browser_state_io_data.mm
@@ -256,7 +256,7 @@
     const base::FilePath& partition_path) const {
   DCHECK(initialized_);
   AppRequestContext* context = nullptr;
-  if (base::ContainsKey(app_request_context_map_, partition_path)) {
+  if (base::Contains(app_request_context_map_, partition_path)) {
     context = app_request_context_map_[partition_path];
   } else {
     context = AcquireIsolatedAppRequestContext(main_context);
@@ -269,7 +269,7 @@
 void ChromeBrowserStateIOData::SetCookieStoreForPartitionPath(
     std::unique_ptr<net::CookieStore> cookie_store,
     const base::FilePath& partition_path) {
-  DCHECK(base::ContainsKey(app_request_context_map_, partition_path));
+  DCHECK(base::Contains(app_request_context_map_, partition_path));
   app_request_context_map_[partition_path]->SetCookieStore(
       std::move(cookie_store));
 }
diff --git a/ios/chrome/browser/metrics/ukm_egtest.mm b/ios/chrome/browser/metrics/ukm_egtest.mm
index d64f6b5ea..1f6bfce 100644
--- a/ios/chrome/browser/metrics/ukm_egtest.mm
+++ b/ios/chrome/browser/metrics/ukm_egtest.mm
@@ -68,7 +68,7 @@
 
   static bool HasDummySource(ukm::SourceId source_id) {
     auto* service = ukm_service();
-    return service && base::ContainsKey(service->sources(), source_id);
+    return service && base::Contains(service->sources(), source_id);
   }
 
   static void RecordDummySource(ukm::SourceId source_id) {
diff --git a/ios/chrome/browser/payments/ios_payment_instrument_finder.mm b/ios/chrome/browser/payments/ios_payment_instrument_finder.mm
index bd74073..c45cd80 100644
--- a/ios/chrome/browser/payments/ios_payment_instrument_finder.mm
+++ b/ios/chrome/browser/payments/ios_payment_instrument_finder.mm
@@ -67,7 +67,7 @@
   for (const GURL& method : queried_url_payment_method_identifiers) {
     // Ensure that the payment method is recognized by looking for an
     // "app-name://" scheme to query for its presence.
-    if (!base::ContainsKey(enum_map, method.spec()))
+    if (!base::Contains(enum_map, method.spec()))
       continue;
 
     // If there is an app that can handle |scheme| on this device, this payment
diff --git a/ios/chrome/browser/reading_list/url_downloader.cc b/ios/chrome/browser/reading_list/url_downloader.cc
index cf22044..7fc4e33f 100644
--- a/ios/chrome/browser/reading_list/url_downloader.cc
+++ b/ios/chrome/browser/reading_list/url_downloader.cc
@@ -83,7 +83,7 @@
 }
 
 void URLDownloader::DownloadOfflineURL(const GURL& url) {
-  if (!base::ContainsValue(tasks_, std::make_pair(DOWNLOAD, url))) {
+  if (!base::Contains(tasks_, std::make_pair(DOWNLOAD, url))) {
     tasks_.push_back(std::make_pair(DOWNLOAD, url));
     HandleNextTask();
   }
diff --git a/ios/chrome/browser/reading_list/url_downloader_unittest.mm b/ios/chrome/browser/reading_list/url_downloader_unittest.mm
index 1cc8de9..42e66bcd 100644
--- a/ios/chrome/browser/reading_list/url_downloader_unittest.mm
+++ b/ios/chrome/browser/reading_list/url_downloader_unittest.mm
@@ -253,7 +253,7 @@
   // Wait for all asynchronous tasks to complete.
   task_environment_.RunUntilIdle();
 
-  ASSERT_TRUE(!base::ContainsValue(downloader_->downloaded_files_, url));
+  ASSERT_TRUE(!base::Contains(downloader_->downloaded_files_, url));
   ASSERT_EQ(1ul, downloader_->downloaded_files_.size());
   ASSERT_EQ(1ul, downloader_->removed_files_.size());
   ASSERT_FALSE(downloader_->CheckExistenceOfOfflineURLPagePath(url));
@@ -272,8 +272,8 @@
   // Wait for all asynchronous tasks to complete.
   task_environment_.RunUntilIdle();
 
-  ASSERT_TRUE(base::ContainsValue(downloader_->downloaded_files_, url));
-  ASSERT_TRUE(base::ContainsValue(downloader_->removed_files_, url));
+  ASSERT_TRUE(base::Contains(downloader_->downloaded_files_, url));
+  ASSERT_TRUE(base::Contains(downloader_->removed_files_, url));
   ASSERT_TRUE(downloader_->CheckExistenceOfOfflineURLPagePath(url));
 }
 
diff --git a/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.mm b/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.mm
index c41dfca..43741c4 100644
--- a/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.mm
@@ -463,27 +463,10 @@
     return;
   base::RecordAction(base::UserMetricsAction(
       "Signin_AccountSettings_GoogleActivityControlsClicked"));
-  UINavigationController* settingsDetails =
-      ios::GetChromeBrowserProvider()
-          ->GetChromeIdentityService()
-          ->CreateWebAndAppSettingDetailsController(
-              [self authService] -> GetAuthenticatedIdentity(), self);
-  UIImage* closeIcon = [ChromeIcon closeIcon];
-  SEL action = @selector(closeGoogleActivitySettings:);
-  [settingsDetails.topViewController navigationItem].leftBarButtonItem =
-      [ChromeIcon templateBarButtonItemWithImage:closeIcon
-                                          target:self
-                                          action:action];
-  [self presentViewController:settingsDetails animated:YES completion:nil];
-
-  // Keep a weak reference on the settings details, to be able to dismiss it
-  // when the primary account is removed.
-  _settingsDetails = settingsDetails;
-}
-
-- (void)closeGoogleActivitySettings:(id)sender {
-  DCHECK(_settingsDetails);
-  [self dismissViewControllerAnimated:YES completion:nil];
+  ios::GetChromeBrowserProvider()
+      ->GetChromeIdentityService()
+      ->PresentWebAndAppSettingDetailsController(
+          [self authService] -> GetAuthenticatedIdentity(), self, YES);
 }
 
 #pragma mark - Authentication operations
diff --git a/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_coordinator.mm b/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_coordinator.mm
index 79a0161..0188f8d 100644
--- a/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_coordinator.mm
@@ -49,11 +49,11 @@
     ManageSyncSettingsTableViewController* viewController;
 // Mediator.
 @property(nonatomic, strong) ManageSyncSettingsMediator* mediator;
-// Web and app activity view controller.
-@property(nonatomic, weak)
-    UINavigationController* webAndAppSettingDetailsController;
 // Sync service.
 @property(nonatomic, assign, readonly) syncer::SyncService* syncService;
+// Dismiss callback for Web and app setting details view.
+@property(nonatomic, copy) ios::DismissWebAndAppSettingDetailsControllerBlock
+    dismissWebAndAppSettingDetailsControllerBlock;
 
 @end
 
@@ -88,20 +88,12 @@
 
 #pragma mark - Private
 
-// Called by the close button of the Web and app activity view controller.
-- (void)closeGoogleActivitySettings:(id)sender {
-  DCHECK(self.webAndAppSettingDetailsController);
-  [self.navigationController dismissViewControllerAnimated:YES completion:nil];
-  self.webAndAppSettingDetailsController = nil;
-}
-
 // Closes the Manage sync settings view controller.
 - (void)closeManageSyncSettings {
   if (self.viewController.navigationController) {
-    if (self.webAndAppSettingDetailsController) {
-      [self.navigationController dismissViewControllerAnimated:NO
-                                                    completion:nil];
-      self.webAndAppSettingDetailsController = nil;
+    if (self.dismissWebAndAppSettingDetailsControllerBlock) {
+      self.dismissWebAndAppSettingDetailsControllerBlock(NO);
+      self.dismissWebAndAppSettingDetailsControllerBlock = nil;
     }
     [self.navigationController popToViewController:self.viewController
                                           animated:NO];
@@ -150,22 +142,12 @@
       AuthenticationServiceFactory::GetForBrowserState(self.browserState);
   base::RecordAction(base::UserMetricsAction(
       "Signin_AccountSettings_GoogleActivityControlsClicked"));
-  DCHECK(!self.webAndAppSettingDetailsController);
-  self.webAndAppSettingDetailsController =
+  self.dismissWebAndAppSettingDetailsControllerBlock =
       ios::GetChromeBrowserProvider()
           ->GetChromeIdentityService()
-          ->CreateWebAndAppSettingDetailsController(
-              authService->GetAuthenticatedIdentity(), self);
-  UIImage* closeIcon = [ChromeIcon closeIcon];
-  SEL action = @selector(closeGoogleActivitySettings:);
-  [self.webAndAppSettingDetailsController.topViewController navigationItem]
-      .leftBarButtonItem = [ChromeIcon templateBarButtonItemWithImage:closeIcon
-                                                               target:self
-                                                               action:action];
-  [self.navigationController
-      presentViewController:self.webAndAppSettingDetailsController
-                   animated:YES
-                 completion:nil];
+          ->PresentWebAndAppSettingDetailsController(
+              authService->GetAuthenticatedIdentity(), self.viewController,
+              YES);
 }
 
 - (void)openDataFromChromeSyncWebPage {
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 c0b0af4..7766b1f 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
@@ -155,9 +155,12 @@
 
 - (void)viewDidLayoutSubviews {
   [super viewDidLayoutSubviews];
-  // TODO(crbug.com/931173): This is a workaround to fix the vertical alignment
-  // of the back button. Remove once the UIKit bug is fixed.
-  [self.navigationController.navigationBar setNeedsLayout];
+  if (@available(iOS 13, *)) {
+  } else {
+    // This is a workaround to fix the vertical alignment of the back button.
+    // The bug has been fixed in iOS 13. See crbug.com/931173 if needed.
+    [self.navigationController.navigationBar setNeedsLayout];
+  }
 }
 
 #pragma mark - UITableViewDelegate
diff --git a/ios/chrome/browser/voice/speech_input_locale_config_impl.mm b/ios/chrome/browser/voice/speech_input_locale_config_impl.mm
index dda5926..910c8868 100644
--- a/ios/chrome/browser/voice/speech_input_locale_config_impl.mm
+++ b/ios/chrome/browser/voice/speech_input_locale_config_impl.mm
@@ -85,7 +85,7 @@
 bool SpeechInputLocaleConfigImpl::IsTextToSpeechEnabledForCode(
     const std::string& locale_code) const {
   std::string language = GetLanguageComponentForLocaleCode(locale_code);
-  return base::ContainsValue(text_to_speech_languages_, language);
+  return base::Contains(text_to_speech_languages_, language);
 }
 
 SpeechInputLocale SpeechInputLocaleConfigImpl::GetMatchingLocale(
diff --git a/ios/public/provider/chrome/browser/signin/chrome_identity_service.h b/ios/public/provider/chrome/browser/signin/chrome_identity_service.h
index b12eddb..19e57cf 100644
--- a/ios/public/provider/chrome/browser/signin/chrome_identity_service.h
+++ b/ios/public/provider/chrome/browser/signin/chrome_identity_service.h
@@ -112,12 +112,18 @@
       id<ChromeIdentityBrowserOpener> browser_opener);
 
   // Returns a new Web and App Setting Details controller to present.
+  // Deprecated, crbug.com/905680.
+  // Please use PresentWebAndAppSettingDetailsController().
   virtual UINavigationController* CreateWebAndAppSettingDetailsController(
       ChromeIdentity* identity,
       id<ChromeIdentityBrowserOpener> browser_opener);
 
-  // Not implemented yet. Please use CreateWebAndAppSettingDetailsController().
-  // See: crbug.com/905680.
+  // Presents a new Web and App Setting Details view.
+  // |identity| the identity used to present the view.
+  // |viewController| the view to present the setting details.
+  // |animated| the view is presented with animation if YES.
+  // Returns a block to dismiss the presented view. This block can be ignored if
+  // not needed.
   virtual DismissWebAndAppSettingDetailsControllerBlock
   PresentWebAndAppSettingDetailsController(ChromeIdentity* identity,
                                            UIViewController* viewController,
diff --git a/ios/web/common/origin_util.mm b/ios/web/common/origin_util.mm
index 2b993b8ef..52a0c46 100644
--- a/ios/web/common/origin_util.mm
+++ b/ios/web/common/origin_util.mm
@@ -28,7 +28,7 @@
     return true;
   }
 
-  if (base::ContainsValue(url::GetSecureSchemes(), url.scheme()))
+  if (base::Contains(url::GetSecureSchemes(), url.scheme()))
     return true;
 
   if (net::IsLocalhost(url))
diff --git a/ios/web/webui/url_data_manager_ios.cc b/ios/web/webui/url_data_manager_ios.cc
index 483c9f5..3415ac26 100644
--- a/ios/web/webui/url_data_manager_ios.cc
+++ b/ios/web/webui/url_data_manager_ios.cc
@@ -135,7 +135,7 @@
   base::AutoLock lock(GetDeleteLock());
   if (!data_sources_)
     return false;
-  return base::ContainsValue(*data_sources_, data_source);
+  return base::Contains(*data_sources_, data_source);
 }
 
 }  // namespace web
diff --git a/ios/web/webui/url_data_manager_ios_backend.mm b/ios/web/webui/url_data_manager_ios_backend.mm
index b8dfc04..bd1f3d2c 100644
--- a/ios/web/webui/url_data_manager_ios_backend.mm
+++ b/ios/web/webui/url_data_manager_ios_backend.mm
@@ -60,7 +60,7 @@
   std::vector<std::string> additional_schemes;
   DCHECK(GetWebClient()->IsAppSpecificURL(url) ||
          (GetWebClient()->GetAdditionalWebUISchemes(&additional_schemes),
-          base::ContainsValue(additional_schemes, url.scheme())));
+          base::Contains(additional_schemes, url.scheme())));
 
   if (!url.is_valid()) {
     NOTREACHED();
diff --git a/ipc/ipc_channel_proxy.cc b/ipc/ipc_channel_proxy.cc
index 11ac875..5abff39 100644
--- a/ipc/ipc_channel_proxy.cc
+++ b/ipc/ipc_channel_proxy.cc
@@ -340,7 +340,7 @@
   DCHECK(default_listener_task_runner_->BelongsToCurrentThread());
   DCHECK(task_runner);
   base::AutoLock lock(listener_thread_task_runners_lock_);
-  if (!base::ContainsKey(listener_thread_task_runners_, routing_id))
+  if (!base::Contains(listener_thread_task_runners_, routing_id))
     listener_thread_task_runners_.insert({routing_id, std::move(task_runner)});
 }
 
@@ -348,7 +348,7 @@
 void ChannelProxy::Context::RemoveListenerTaskRunner(int32_t routing_id) {
   DCHECK(default_listener_task_runner_->BelongsToCurrentThread());
   base::AutoLock lock(listener_thread_task_runners_lock_);
-  if (base::ContainsKey(listener_thread_task_runners_, routing_id))
+  if (base::Contains(listener_thread_task_runners_, routing_id))
     listener_thread_task_runners_.erase(routing_id);
 }
 
diff --git a/media/renderers/video_resource_updater.cc b/media/renderers/video_resource_updater.cc
index f15eafd..acba960 100644
--- a/media/renderers/video_resource_updater.cc
+++ b/media/renderers/video_resource_updater.cc
@@ -284,9 +284,8 @@
         viz::bitmap_allocation::AllocateSharedBitmap(
             resource_size(), viz::ResourceFormat::RGBA_8888);
     shared_mapping_ = std::move(shm.mapping);
-    shared_bitmap_reporter_->DidAllocateSharedBitmap(
-        viz::bitmap_allocation::ToMojoHandle(std::move(shm.region)),
-        shared_bitmap_id_);
+    shared_bitmap_reporter_->DidAllocateSharedBitmap(std::move(shm.region),
+                                                     shared_bitmap_id_);
   }
   ~SoftwarePlaneResource() override {
     shared_bitmap_reporter_->DidDeleteSharedBitmap(shared_bitmap_id_);
diff --git a/media/renderers/video_resource_updater_unittest.cc b/media/renderers/video_resource_updater_unittest.cc
index fdeeb77..8e4edf88 100644
--- a/media/renderers/video_resource_updater_unittest.cc
+++ b/media/renderers/video_resource_updater_unittest.cc
@@ -8,6 +8,7 @@
 #include <stdint.h>
 
 #include "base/bind.h"
+#include "base/memory/read_only_shared_memory_region.h"
 #include "base/test/scoped_task_environment.h"
 #include "components/viz/client/client_resource_provider.h"
 #include "components/viz/client/shared_bitmap_reporter.h"
@@ -26,7 +27,7 @@
   ~FakeSharedBitmapReporter() override = default;
 
   // viz::SharedBitmapReporter implementation.
-  void DidAllocateSharedBitmap(mojo::ScopedSharedBufferHandle buffer,
+  void DidAllocateSharedBitmap(base::ReadOnlySharedMemoryRegion region,
                                const viz::SharedBitmapId& id) override {
     DCHECK_EQ(shared_bitmaps_.count(id), 0u);
     shared_bitmaps_.insert(id);
diff --git a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
index 51e4171f..80efe7a3 100644
--- a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
+++ b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
@@ -299,7 +299,7 @@
   controller_->SyncWatch(&response_received);
   // Make sure that this instance hasn't been destroyed.
   if (weak_self) {
-    DCHECK(base::ContainsKey(sync_responses_, request_id));
+    DCHECK(base::Contains(sync_responses_, request_id));
     auto iter = sync_responses_.find(request_id);
     DCHECK_EQ(&response_received, iter->second->response_received);
     if (response_received) {
diff --git a/mojo/public/cpp/bindings/lib/multiplex_router.cc b/mojo/public/cpp/bindings/lib/multiplex_router.cc
index 3fc58449c..07a2748c0 100644
--- a/mojo/public/cpp/bindings/lib/multiplex_router.cc
+++ b/mojo/public/cpp/bindings/lib/multiplex_router.cc
@@ -386,7 +386,7 @@
       id = next_interface_id_value_++;
       if (set_interface_id_namespace_bit_)
         id |= kInterfaceIdNamespaceMask;
-    } while (base::ContainsKey(endpoints_, id));
+    } while (base::Contains(endpoints_, id));
 
     InterfaceEndpoint* endpoint = new InterfaceEndpoint(this, id);
     endpoints_[id] = endpoint;
@@ -438,7 +438,7 @@
     return;
 
   MayAutoLock locker(&lock_);
-  DCHECK(base::ContainsKey(endpoints_, id));
+  DCHECK(base::Contains(endpoints_, id));
   InterfaceEndpoint* endpoint = endpoints_[id].get();
   DCHECK(!endpoint->client());
   DCHECK(!endpoint->closed());
@@ -462,7 +462,7 @@
   DCHECK(client);
 
   MayAutoLock locker(&lock_);
-  DCHECK(base::ContainsKey(endpoints_, id));
+  DCHECK(base::Contains(endpoints_, id));
 
   InterfaceEndpoint* endpoint = endpoints_[id].get();
   endpoint->AttachClient(client, std::move(runner));
@@ -481,7 +481,7 @@
   DCHECK(IsValidInterfaceId(id));
 
   MayAutoLock locker(&lock_);
-  DCHECK(base::ContainsKey(endpoints_, id));
+  DCHECK(base::Contains(endpoints_, id));
 
   InterfaceEndpoint* endpoint = endpoints_[id].get();
   endpoint->DetachClient();
@@ -549,7 +549,7 @@
   if (endpoints_.size() == 0)
     return false;
 
-  return !base::ContainsKey(endpoints_, kMasterInterfaceId);
+  return !base::Contains(endpoints_, kMasterInterfaceId);
 }
 
 void MultiplexRouter::EnableBatchDispatch() {
diff --git a/mojo/public/cpp/bindings/lib/sync_handle_registry.cc b/mojo/public/cpp/bindings/lib/sync_handle_registry.cc
index 2ae6353..b91d5f9 100644
--- a/mojo/public/cpp/bindings/lib/sync_handle_registry.cc
+++ b/mojo/public/cpp/bindings/lib/sync_handle_registry.cc
@@ -36,7 +36,7 @@
                                         const HandleCallback& callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  if (base::ContainsKey(handles_, handle))
+  if (base::Contains(handles_, handle))
     return false;
 
   MojoResult result = wait_set_.AddHandle(handle, handle_signals);
@@ -49,7 +49,7 @@
 
 void SyncHandleRegistry::UnregisterHandle(const Handle& handle) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!base::ContainsKey(handles_, handle))
+  if (!base::Contains(handles_, handle))
     return;
 
   MojoResult result = wait_set_.RemoveHandle(handle);
diff --git a/net/base/expiring_cache_unittest.cc b/net/base/expiring_cache_unittest.cc
index f1414f9..1f3c5d38 100644
--- a/net/base/expiring_cache_unittest.cc
+++ b/net/base/expiring_cache_unittest.cc
@@ -119,16 +119,16 @@
   }
   EXPECT_EQ(10U, cache.size());
 
-  EXPECT_TRUE(base::ContainsKey(cache.entries_, "valid0"));
-  EXPECT_TRUE(base::ContainsKey(cache.entries_, "valid1"));
-  EXPECT_TRUE(base::ContainsKey(cache.entries_, "valid2"));
-  EXPECT_TRUE(base::ContainsKey(cache.entries_, "valid3"));
-  EXPECT_TRUE(base::ContainsKey(cache.entries_, "valid4"));
-  EXPECT_TRUE(base::ContainsKey(cache.entries_, "expired0"));
-  EXPECT_TRUE(base::ContainsKey(cache.entries_, "expired1"));
-  EXPECT_TRUE(base::ContainsKey(cache.entries_, "expired2"));
-  EXPECT_TRUE(base::ContainsKey(cache.entries_, "negative0"));
-  EXPECT_TRUE(base::ContainsKey(cache.entries_, "negative1"));
+  EXPECT_TRUE(base::Contains(cache.entries_, "valid0"));
+  EXPECT_TRUE(base::Contains(cache.entries_, "valid1"));
+  EXPECT_TRUE(base::Contains(cache.entries_, "valid2"));
+  EXPECT_TRUE(base::Contains(cache.entries_, "valid3"));
+  EXPECT_TRUE(base::Contains(cache.entries_, "valid4"));
+  EXPECT_TRUE(base::Contains(cache.entries_, "expired0"));
+  EXPECT_TRUE(base::Contains(cache.entries_, "expired1"));
+  EXPECT_TRUE(base::Contains(cache.entries_, "expired2"));
+  EXPECT_TRUE(base::Contains(cache.entries_, "negative0"));
+  EXPECT_TRUE(base::Contains(cache.entries_, "negative1"));
 
   // Shrink the new max constraints bound and compact. The "negative" and
   // "expired" entries should be dropped.
@@ -136,16 +136,16 @@
   cache.Compact(now);
   EXPECT_EQ(5U, cache.size());
 
-  EXPECT_TRUE(base::ContainsKey(cache.entries_, "valid0"));
-  EXPECT_TRUE(base::ContainsKey(cache.entries_, "valid1"));
-  EXPECT_TRUE(base::ContainsKey(cache.entries_, "valid2"));
-  EXPECT_TRUE(base::ContainsKey(cache.entries_, "valid3"));
-  EXPECT_TRUE(base::ContainsKey(cache.entries_, "valid4"));
-  EXPECT_FALSE(base::ContainsKey(cache.entries_, "expired0"));
-  EXPECT_FALSE(base::ContainsKey(cache.entries_, "expired1"));
-  EXPECT_FALSE(base::ContainsKey(cache.entries_, "expired2"));
-  EXPECT_FALSE(base::ContainsKey(cache.entries_, "negative0"));
-  EXPECT_FALSE(base::ContainsKey(cache.entries_, "negative1"));
+  EXPECT_TRUE(base::Contains(cache.entries_, "valid0"));
+  EXPECT_TRUE(base::Contains(cache.entries_, "valid1"));
+  EXPECT_TRUE(base::Contains(cache.entries_, "valid2"));
+  EXPECT_TRUE(base::Contains(cache.entries_, "valid3"));
+  EXPECT_TRUE(base::Contains(cache.entries_, "valid4"));
+  EXPECT_FALSE(base::Contains(cache.entries_, "expired0"));
+  EXPECT_FALSE(base::Contains(cache.entries_, "expired1"));
+  EXPECT_FALSE(base::Contains(cache.entries_, "expired2"));
+  EXPECT_FALSE(base::Contains(cache.entries_, "negative0"));
+  EXPECT_FALSE(base::Contains(cache.entries_, "negative1"));
 
   // Shrink further -- this time the compact will start dropping valid entries
   // to make space.
diff --git a/net/base/features.cc b/net/base/features.cc
index ae53c9071..502fdeac 100644
--- a/net/base/features.cc
+++ b/net/base/features.cc
@@ -13,7 +13,7 @@
                                           base::FEATURE_ENABLED_BY_DEFAULT};
 
 const base::Feature kCapRefererHeaderLength = {
-    "CapRefererHeaderLength", base::FEATURE_DISABLED_BY_DEFAULT};
+    "CapRefererHeaderLength", base::FEATURE_ENABLED_BY_DEFAULT};
 const base::FeatureParam<int> kMaxRefererHeaderLength = {
     &kCapRefererHeaderLength, "MaxRefererHeaderLength", 4096};
 
diff --git a/net/base/filename_util_internal.cc b/net/base/filename_util_internal.cc
index 46dc33a..b8ce2e0 100644
--- a/net/base/filename_util_internal.cc
+++ b/net/base/filename_util_internal.cc
@@ -55,7 +55,7 @@
   // "foo.jpg" to "foo.jpeg".
   std::vector<base::FilePath::StringType> all_mime_extensions;
   GetExtensionsForMimeType(mime_type, &all_mime_extensions);
-  if (base::ContainsValue(all_mime_extensions, extension))
+  if (base::Contains(all_mime_extensions, extension))
     return extension;
 
   // Get the "final" extension. In most cases, this is the same as the
@@ -69,7 +69,7 @@
   // If there's a double extension, and the second extension is in the
   // list of valid extensions for the given type, keep the double extension.
   // This avoids renaming things like "foo.tar.gz" to "foo.gz".
-  if (base::ContainsValue(all_mime_extensions, final_extension))
+  if (base::Contains(all_mime_extensions, final_extension))
     return extension;
   return preferred_mime_extension;
 }
diff --git a/net/base/mime_util_unittest.cc b/net/base/mime_util_unittest.cc
index 6c224ed..ade4192 100644
--- a/net/base/mime_util_unittest.cc
+++ b/net/base/mime_util_unittest.cc
@@ -330,7 +330,7 @@
           test.contained_result,
           test.contained_result + strlen(test.contained_result));
 
-      bool found = base::ContainsValue(extensions, contained_result);
+      bool found = base::Contains(extensions, contained_result);
 
       ASSERT_TRUE(found) << "Must find at least the contained result within "
                          << test.mime_type;
diff --git a/net/base/network_throttle_manager_impl.cc b/net/base/network_throttle_manager_impl.cc
index d5043bc..0c0e7ec 100644
--- a/net/base/network_throttle_manager_impl.cc
+++ b/net/base/network_throttle_manager_impl.cc
@@ -240,8 +240,8 @@
       break;
   }
 
-  DCHECK(!base::ContainsValue(blocked_throttles_, throttle));
-  DCHECK(!base::ContainsValue(outstanding_throttles_, throttle));
+  DCHECK(!base::Contains(blocked_throttles_, throttle));
+  DCHECK(!base::Contains(outstanding_throttles_, throttle));
 
   // Unblock the throttles if there's some chance there's a throttle to
   // unblock.
diff --git a/net/cert/x509_certificate.cc b/net/cert/x509_certificate.cc
index f196769..a5d1616 100644
--- a/net/cert/x509_certificate.cc
+++ b/net/cert/x509_certificate.cc
@@ -439,13 +439,13 @@
   std::string normalized_cert_issuer;
   if (!GetNormalizedCertIssuer(cert_buffer_.get(), &normalized_cert_issuer))
     return false;
-  if (base::ContainsValue(normalized_issuers, normalized_cert_issuer))
+  if (base::Contains(normalized_issuers, normalized_cert_issuer))
     return true;
 
   for (const auto& intermediate : intermediate_ca_certs_) {
     if (!GetNormalizedCertIssuer(intermediate.get(), &normalized_cert_issuer))
       return false;
-    if (base::ContainsValue(normalized_issuers, normalized_cert_issuer))
+    if (base::Contains(normalized_issuers, normalized_cert_issuer))
       return true;
   }
   return false;
@@ -488,7 +488,7 @@
     base::StringPiece ip_addr_string(
         reinterpret_cast<const char*>(host_info.address),
         host_info.AddressLength());
-    return base::ContainsValue(cert_san_ip_addrs, ip_addr_string);
+    return base::Contains(cert_san_ip_addrs, ip_addr_string);
   }
 
   // |reference_domain| is the remainder of |host| after the leading host
diff --git a/net/cert_net/nss_ocsp.cc b/net/cert_net/nss_ocsp.cc
index 9fa92efec..826d297 100644
--- a/net/cert_net/nss_ocsp.cc
+++ b/net/cert_net/nss_ocsp.cc
@@ -519,12 +519,12 @@
 }
 
 void OCSPIOLoop::AddRequest(OCSPRequestSession* request) {
-  DCHECK(!base::ContainsKey(requests_, request));
+  DCHECK(!base::Contains(requests_, request));
   requests_.insert(request);
 }
 
 void OCSPIOLoop::RemoveRequest(OCSPRequestSession* request) {
-  DCHECK(base::ContainsKey(requests_, request));
+  DCHECK(base::Contains(requests_, request));
   requests_.erase(request);
 }
 
diff --git a/net/cookies/cookie_monster.cc b/net/cookies/cookie_monster.cc
index 5772e2a..a13fb0f 100644
--- a/net/cookies/cookie_monster.cc
+++ b/net/cookies/cookie_monster.cc
@@ -523,7 +523,7 @@
 bool CookieMonster::IsCookieableScheme(const std::string& scheme) {
   DCHECK(thread_checker_.CalledOnValidThread());
 
-  return base::ContainsValue(cookieable_schemes_, scheme);
+  return base::Contains(cookieable_schemes_, scheme);
 }
 
 const char* const CookieMonster::kDefaultCookieableSchemes[] = {"http", "https",
diff --git a/net/cookies/parse_cookie_line_fuzzer.cc b/net/cookies/parse_cookie_line_fuzzer.cc
index 984a081..beb8401 100644
--- a/net/cookies/parse_cookie_line_fuzzer.cc
+++ b/net/cookies/parse_cookie_line_fuzzer.cc
@@ -22,40 +22,46 @@
   const std::string cookie_line = GetArbitraryString(&data_provider);
   net::ParsedCookie parsed_cookie(cookie_line);
 
-  // Call zero or one of ParsedCookie's mutator methods.
-  switch (data_provider.ConsumeIntegralInRange(0, 10)) {
-    case 0:
-      break;
+  // Call zero or one of ParsedCookie's mutator methods.  Should not call
+  // anything other than SetName/SetValue when !IsValid().
+  const uint8_t action = data_provider.ConsumeIntegralInRange(0, 10);
+  switch (action) {
     case 1:
       parsed_cookie.SetName(GetArbitraryString(&data_provider));
       break;
     case 2:
       parsed_cookie.SetValue(GetArbitraryString(&data_provider));
       break;
-    case 3:
-      parsed_cookie.SetPath(GetArbitraryString(&data_provider));
-      break;
-    case 4:
-      parsed_cookie.SetDomain(GetArbitraryString(&data_provider));
-      break;
-    case 5:
-      parsed_cookie.SetExpires(GetArbitraryString(&data_provider));
-      break;
-    case 6:
-      parsed_cookie.SetMaxAge(GetArbitraryString(&data_provider));
-      break;
-    case 7:
-      parsed_cookie.SetIsSecure(data_provider.ConsumeBool());
-      break;
-    case 8:
-      parsed_cookie.SetIsHttpOnly(data_provider.ConsumeBool());
-      break;
-    case 9:
-      parsed_cookie.SetSameSite(GetArbitraryString(&data_provider));
-      break;
-    case 10:
-      parsed_cookie.SetPriority(GetArbitraryString(&data_provider));
-      break;
+  }
+
+  if (parsed_cookie.IsValid()) {
+    switch (action) {
+      case 3:
+        if (parsed_cookie.IsValid())
+          parsed_cookie.SetPath(GetArbitraryString(&data_provider));
+        break;
+      case 4:
+        parsed_cookie.SetDomain(GetArbitraryString(&data_provider));
+        break;
+      case 5:
+        parsed_cookie.SetExpires(GetArbitraryString(&data_provider));
+        break;
+      case 6:
+        parsed_cookie.SetMaxAge(GetArbitraryString(&data_provider));
+        break;
+      case 7:
+        parsed_cookie.SetIsSecure(data_provider.ConsumeBool());
+        break;
+      case 8:
+        parsed_cookie.SetIsHttpOnly(data_provider.ConsumeBool());
+        break;
+      case 9:
+        parsed_cookie.SetSameSite(GetArbitraryString(&data_provider));
+        break;
+      case 10:
+        parsed_cookie.SetPriority(GetArbitraryString(&data_provider));
+        break;
+    }
   }
 
   // Check that serialize/deserialize inverse property holds for valid cookies.
@@ -63,8 +69,17 @@
     const std::string serialized = parsed_cookie.ToCookieLine();
     net::ParsedCookie reparsed_cookie(serialized);
     const std::string reserialized = reparsed_cookie.ToCookieLine();
-    CHECK(reparsed_cookie.IsValid());
-    CHECK_EQ(serialized, reserialized);
+
+    // RFC6265 requires semicolons to be followed by spaces. Because our parser
+    // permits this rule to be broken, but follows the rule in ToCookieLine(),
+    // it's possible to serialize a string that's longer than the original
+    // input. If the serialized string exceeds kMaxCookieSize, the parser will
+    // reject it. For this fuzzer, we are considering this situation a false
+    // positive.
+    if (serialized.size() <= net::ParsedCookie::kMaxCookieSize) {
+      CHECK(reparsed_cookie.IsValid());
+      CHECK_EQ(serialized, reserialized);
+    }
   }
 
   return 0;
diff --git a/net/disk_cache/simple/simple_backend_impl.cc b/net/disk_cache/simple/simple_backend_impl.cc
index e8eab5d2..76d510d 100644
--- a/net/disk_cache/simple/simple_backend_impl.cc
+++ b/net/disk_cache/simple/simple_backend_impl.cc
@@ -819,12 +819,19 @@
   } else {
     bool mtime_result =
         disk_cache::simple_util::GetMTime(path, &result.cache_dir_mtime);
-    DCHECK(mtime_result);
-    if (!result.max_size) {
+    if (!mtime_result) {
+      // Something deleted the directory between when we set it up and the
+      // mstat; this is not uncommon on some test fixtures which erase their
+      // tempdir while some worker threads may still be running.
+      LOG(ERROR) << "Simple Cache Backend: cache directory inaccessible right "
+                    "after creation; path: "
+                 << path.LossyDisplayName();
+      result.net_error = net::ERR_FAILED;
+    } else if (!result.max_size) {
       int64_t available = base::SysInfo::AmountOfFreeDiskSpace(path);
       result.max_size = disk_cache::PreferredCacheSize(available);
+      DCHECK(result.max_size);
     }
-    DCHECK(result.max_size);
   }
   return result;
 }
diff --git a/net/http/http_auth_handler_factory.cc b/net/http/http_auth_handler_factory.cc
index d44ad9c6..d3242e0 100644
--- a/net/http/http_auth_handler_factory.cc
+++ b/net/http/http_auth_handler_factory.cc
@@ -147,17 +147,17 @@
 
   std::unique_ptr<HttpAuthHandlerRegistryFactory> registry_factory(
       new HttpAuthHandlerRegistryFactory());
-  if (base::ContainsKey(auth_schemes_set, kBasicAuthScheme)) {
+  if (base::Contains(auth_schemes_set, kBasicAuthScheme)) {
     registry_factory->RegisterSchemeFactory(
         kBasicAuthScheme, new HttpAuthHandlerBasic::Factory());
   }
 
-  if (base::ContainsKey(auth_schemes_set, kDigestAuthScheme)) {
+  if (base::Contains(auth_schemes_set, kDigestAuthScheme)) {
     registry_factory->RegisterSchemeFactory(
         kDigestAuthScheme, new HttpAuthHandlerDigest::Factory());
   }
 
-  if (base::ContainsKey(auth_schemes_set, kNtlmAuthScheme)) {
+  if (base::Contains(auth_schemes_set, kNtlmAuthScheme)) {
     HttpAuthHandlerNTLM::Factory* ntlm_factory =
         new HttpAuthHandlerNTLM::Factory();
 #if defined(OS_WIN)
@@ -167,7 +167,7 @@
   }
 
 #if BUILDFLAG(USE_KERBEROS)
-  if (base::ContainsKey(auth_schemes_set, kNegotiateAuthScheme)) {
+  if (base::Contains(auth_schemes_set, kNegotiateAuthScheme)) {
     HttpAuthHandlerNegotiate::Factory* negotiate_factory =
         new HttpAuthHandlerNegotiate::Factory(negotiate_auth_system_factory);
 #if defined(OS_WIN)
diff --git a/net/http/http_cache_lookup_manager.cc b/net/http/http_cache_lookup_manager.cc
index 6fd010f..db6240b 100644
--- a/net/http/http_cache_lookup_manager.cc
+++ b/net/http/http_cache_lookup_manager.cc
@@ -71,7 +71,7 @@
   GURL pushed_url = push_helper->GetURL();
 
   // There's a pending lookup transaction sent over already.
-  if (base::ContainsKey(lookup_transactions_, pushed_url))
+  if (base::Contains(lookup_transactions_, pushed_url))
     return;
 
   auto lookup = std::make_unique<LookupTransaction>(std::move(push_helper),
diff --git a/net/http/http_network_session.cc b/net/http/http_network_session.cc
index d3ed27d..e66c2ce 100644
--- a/net/http/http_network_session.cc
+++ b/net/http/http_network_session.cc
@@ -293,14 +293,14 @@
 
 void HttpNetworkSession::AddResponseDrainer(
     std::unique_ptr<HttpResponseBodyDrainer> drainer) {
-  DCHECK(!base::ContainsKey(response_drainers_, drainer.get()));
+  DCHECK(!base::Contains(response_drainers_, drainer.get()));
   HttpResponseBodyDrainer* drainer_ptr = drainer.get();
   response_drainers_[drainer_ptr] = std::move(drainer);
 }
 
 void HttpNetworkSession::RemoveResponseDrainer(
     HttpResponseBodyDrainer* drainer) {
-  DCHECK(base::ContainsKey(response_drainers_, drainer));
+  DCHECK(base::Contains(response_drainers_, drainer));
   response_drainers_[drainer].release();
   response_drainers_.erase(drainer);
 }
diff --git a/net/http/http_server_properties_impl.cc b/net/http/http_server_properties_impl.cc
index f18530db..c6c7148 100644
--- a/net/http/http_server_properties_impl.cc
+++ b/net/http/http_server_properties_impl.cc
@@ -92,7 +92,7 @@
     url::SchemeHostPort canonical_server(kCanonicalScheme, canonical_suffix,
                                          kCanonicalPort);
     // If we already have a valid canonical server, we're done.
-    if (base::ContainsKey(canonical_alt_svc_map_, canonical_server) &&
+    if (base::Contains(canonical_alt_svc_map_, canonical_server) &&
         (alternative_service_map_.Peek(
              canonical_alt_svc_map_[canonical_server]) !=
          alternative_service_map_.end())) {
diff --git a/net/http/http_stream_factory.cc b/net/http/http_stream_factory.cc
index 3bca1d7..1413fd4 100644
--- a/net/http/http_stream_factory.cc
+++ b/net/http/http_stream_factory.cc
@@ -256,8 +256,8 @@
   PreconnectingProxyServer preconnecting_proxy_server(proxy_info.proxy_server(),
                                                       privacy_mode);
 
-  if (base::ContainsKey(preconnecting_proxy_servers_,
-                        preconnecting_proxy_server)) {
+  if (base::Contains(preconnecting_proxy_servers_,
+                     preconnecting_proxy_server)) {
     UMA_HISTOGRAM_EXACT_LINEAR("Net.PreconnectSkippedToProxyServers", 1, 2);
     // Skip preconnect to the proxy server since we are already preconnecting
     // (probably via some other job). See https://crbug.com/682041 for details.
diff --git a/net/http/http_stream_factory_job.cc b/net/http/http_stream_factory_job.cc
index 0994bf1..45e062f5 100644
--- a/net/http/http_stream_factory_job.cc
+++ b/net/http/http_stream_factory_job.cc
@@ -362,10 +362,10 @@
   // handled by the socket pools, using an HttpProxyConnectJob.
   if (proxy_info.is_quic())
     return !using_ssl;
-  return (base::ContainsKey(session->params().origins_to_force_quic_on,
-                            HostPortPair()) ||
-          base::ContainsKey(session->params().origins_to_force_quic_on,
-                            destination)) &&
+  return (base::Contains(session->params().origins_to_force_quic_on,
+                         HostPortPair()) ||
+          base::Contains(session->params().origins_to_force_quic_on,
+                         destination)) &&
          proxy_info.is_direct() && origin_url.SchemeIs(url::kHttpsScheme);
 }
 
diff --git a/net/http/http_stream_factory_job_controller.cc b/net/http/http_stream_factory_job_controller.cc
index ad0ad9d..3ec5177 100644
--- a/net/http/http_stream_factory_job_controller.cc
+++ b/net/http/http_stream_factory_job_controller.cc
@@ -1243,7 +1243,7 @@
     return true;
 
   std::string lowered_host = base::ToLowerASCII(host);
-  return base::ContainsKey(host_whitelist, lowered_host);
+  return base::Contains(host_whitelist, lowered_host);
 }
 
 }  // namespace net
diff --git a/net/http/transport_security_state.cc b/net/http/transport_security_state.cc
index b7a2bca..275553cd 100644
--- a/net/http/transport_security_state.cc
+++ b/net/http/transport_security_state.cc
@@ -218,7 +218,7 @@
 bool HashesIntersect(const HashValueVector& a,
                      const HashValueVector& b) {
   for (const auto& hash : a) {
-    if (base::ContainsValue(b, hash))
+    if (base::Contains(b, hash))
       return true;
   }
   return false;
diff --git a/net/log/net_log.cc b/net/log/net_log.cc
index 88b3fa8..d0074d7d 100644
--- a/net/log/net_log.cc
+++ b/net/log/net_log.cc
@@ -191,7 +191,7 @@
 
 bool NetLog::HasObserver(ThreadSafeObserver* observer) {
   lock_.AssertAcquired();
-  return base::ContainsValue(observers_, observer);
+  return base::Contains(observers_, observer);
 }
 
 // static
diff --git a/net/quic/platform/impl/quic_map_util_impl.h b/net/quic/platform/impl/quic_map_util_impl.h
index 2efde24..c25e1c4 100644
--- a/net/quic/platform/impl/quic_map_util_impl.h
+++ b/net/quic/platform/impl/quic_map_util_impl.h
@@ -11,12 +11,12 @@
 
 template <class Collection, class Key>
 bool QuicContainsKeyImpl(const Collection& collection, const Key& key) {
-  return base::ContainsKey(collection, key);
+  return base::Contains(collection, key);
 }
 
 template <typename Collection, typename Value>
 bool QuicContainsValueImpl(const Collection& collection, const Value& value) {
-  return base::ContainsValue(collection, value);
+  return base::Contains(collection, value);
 }
 
 }  // namespace quic
diff --git a/net/quic/quic_chromium_client_session.cc b/net/quic/quic_chromium_client_session.cc
index c216b1f..be2baea 100644
--- a/net/quic/quic_chromium_client_session.cc
+++ b/net/quic/quic_chromium_client_session.cc
@@ -1012,12 +1012,12 @@
     return;
   }
 
-  DCHECK(!base::ContainsKey(handles_, handle));
+  DCHECK(!base::Contains(handles_, handle));
   handles_.insert(handle);
 }
 
 void QuicChromiumClientSession::RemoveHandle(Handle* handle) {
-  DCHECK(base::ContainsKey(handles_, handle));
+  DCHECK(base::Contains(handles_, handle));
   handles_.erase(handle);
 }
 
diff --git a/net/quic/quic_stream_factory.cc b/net/quic/quic_stream_factory.cc
index 9dfa951..cb11203 100644
--- a/net/quic/quic_stream_factory.cc
+++ b/net/quic/quic_stream_factory.cc
@@ -1284,7 +1284,7 @@
   if (active_sessions_.empty())
     return false;
 
-  if (base::ContainsKey(active_sessions_, session_key))
+  if (base::Contains(active_sessions_, session_key))
     return true;
 
   for (const auto& key_value : active_sessions_) {
@@ -1450,7 +1450,7 @@
   const quic::QuicServerId& server_id(key.server_id());
   DCHECK(!HasActiveSession(key.session_key()));
   for (const IPEndPoint& address : address_list) {
-    if (!base::ContainsKey(ip_aliases_, address))
+    if (!base::Contains(ip_aliases_, address))
       continue;
 
     const SessionSet& sessions = ip_aliases_[address];
@@ -1515,7 +1515,7 @@
   }
   ProcessGoingAwaySession(session, all_sessions_[session].server_id(), false);
   if (!aliases.empty()) {
-    DCHECK(base::ContainsKey(session_peer_ip_, session));
+    DCHECK(base::Contains(session_peer_ip_, session));
     const IPEndPoint peer_address = session_peer_ip_[session];
     ip_aliases_[peer_address].erase(session);
     if (ip_aliases_[peer_address].empty())
@@ -1729,16 +1729,16 @@
   // TODO(rtenneti): crbug.com/498823 - delete active_sessions_.empty() check.
   if (active_sessions_.empty())
     return false;
-  return base::ContainsKey(active_sessions_, session_key);
+  return base::Contains(active_sessions_, session_key);
 }
 
 bool QuicStreamFactory::HasActiveJob(const QuicSessionKey& session_key) const {
-  return base::ContainsKey(active_jobs_, session_key);
+  return base::Contains(active_jobs_, session_key);
 }
 
 bool QuicStreamFactory::HasActiveCertVerifierJob(
     const quic::QuicServerId& server_id) const {
-  return base::ContainsKey(active_cert_verifier_jobs_, server_id);
+  return base::Contains(active_cert_verifier_jobs_, server_id);
 }
 
 int QuicStreamFactory::ConfigureSocket(DatagramClientSocket* socket,
@@ -1921,7 +1921,7 @@
   writer->set_delegate(*session);
 
   (*session)->Initialize();
-  bool closed_during_initialize = !base::ContainsKey(all_sessions_, *session) ||
+  bool closed_during_initialize = !base::Contains(all_sessions_, *session) ||
                                   !(*session)->connection()->connected();
   UMA_HISTOGRAM_BOOLEAN("Net.QuicSession.ClosedDuringInitializeSession",
                         closed_during_initialize);
@@ -1946,9 +1946,9 @@
   session_aliases_[session].insert(key);
   const IPEndPoint peer_address =
       ToIPEndPoint(session->connection()->peer_address());
-  DCHECK(!base::ContainsKey(ip_aliases_[peer_address], session));
+  DCHECK(!base::Contains(ip_aliases_[peer_address], session));
   ip_aliases_[peer_address].insert(session);
-  DCHECK(!base::ContainsKey(session_peer_ip_, session));
+  DCHECK(!base::Contains(session_peer_ip_, session));
   session_peer_ip_[session] = peer_address;
 }
 
diff --git a/net/reporting/reporting_cache_impl.cc b/net/reporting/reporting_cache_impl.cc
index 01f343d..004a6bc 100644
--- a/net/reporting/reporting_cache_impl.cc
+++ b/net/reporting/reporting_cache_impl.cc
@@ -53,7 +53,7 @@
   // all remaining reports (doomed or not).
   for (auto it = reports_.begin(); it != reports_.end(); ++it) {
     ReportingReport* report = it->second.get();
-    if (!base::ContainsKey(doomed_reports_, report))
+    if (!base::Contains(doomed_reports_, report))
       report->outcome = ReportingReport::Outcome::ERASED_REPORTING_SHUT_DOWN;
     report->RecordOutcome(now);
   }
@@ -84,7 +84,7 @@
     DCHECK_NE(nullptr, to_evict);
     // The newly-added report isn't pending, so even if all other reports are
     // pending, the cache should have a report to evict.
-    DCHECK(!base::ContainsKey(pending_reports_, to_evict));
+    DCHECK(!base::Contains(pending_reports_, to_evict));
     reports_[to_evict]->outcome = ReportingReport::Outcome::ERASED_EVICTED;
     RemoveReportInternal(to_evict);
   }
@@ -96,7 +96,7 @@
     std::vector<const ReportingReport*>* reports_out) const {
   reports_out->clear();
   for (const auto& it : reports_) {
-    if (!base::ContainsKey(doomed_reports_, it.first))
+    if (!base::Contains(doomed_reports_, it.first))
       reports_out->push_back(it.second.get());
   }
 }
@@ -131,9 +131,9 @@
     if (report->body) {
       report_dict.SetKey("body", report->body->Clone());
     }
-    if (base::ContainsKey(doomed_reports_, report)) {
+    if (base::Contains(doomed_reports_, report)) {
       report_dict.SetKey("status", base::Value("doomed"));
-    } else if (base::ContainsKey(pending_reports_, report)) {
+    } else if (base::Contains(pending_reports_, report)) {
       report_dict.SetKey("status", base::Value("pending"));
     } else {
       report_dict.SetKey("status", base::Value("queued"));
@@ -147,8 +147,8 @@
     std::vector<const ReportingReport*>* reports_out) const {
   reports_out->clear();
   for (const auto& it : reports_) {
-    if (!base::ContainsKey(pending_reports_, it.first) &&
-        !base::ContainsKey(doomed_reports_, it.first)) {
+    if (!base::Contains(pending_reports_, it.first) &&
+        !base::Contains(doomed_reports_, it.first)) {
       reports_out->push_back(it.second.get());
     }
   }
@@ -169,7 +169,7 @@
   for (const ReportingReport* report : reports) {
     size_t erased = pending_reports_.erase(report);
     DCHECK_EQ(1u, erased);
-    if (base::ContainsKey(doomed_reports_, report)) {
+    if (base::Contains(doomed_reports_, report)) {
       reports_to_remove.push_back(report);
       doomed_reports_.erase(report);
     }
@@ -182,7 +182,7 @@
 void ReportingCacheImpl::IncrementReportsAttempts(
     const std::vector<const ReportingReport*>& reports) {
   for (const ReportingReport* report : reports) {
-    DCHECK(base::ContainsKey(reports_, report));
+    DCHECK(base::Contains(reports_, report));
     reports_[report]->attempts++;
   }
 
@@ -216,10 +216,10 @@
     ReportingReport::Outcome outcome) {
   for (const ReportingReport* report : reports) {
     reports_[report]->outcome = outcome;
-    if (base::ContainsKey(pending_reports_, report)) {
+    if (base::Contains(pending_reports_, report)) {
       doomed_reports_.insert(report);
     } else {
-      DCHECK(!base::ContainsKey(doomed_reports_, report));
+      DCHECK(!base::Contains(doomed_reports_, report));
       RemoveReportInternal(report);
     }
   }
@@ -232,7 +232,7 @@
   for (auto it = reports_.begin(); it != reports_.end(); ++it) {
     ReportingReport* report = it->second.get();
     report->outcome = outcome;
-    if (!base::ContainsKey(pending_reports_, report))
+    if (!base::Contains(pending_reports_, report))
       reports_to_remove.push_back(report);
     else
       doomed_reports_.insert(report);
@@ -250,12 +250,12 @@
 
 bool ReportingCacheImpl::IsReportPendingForTesting(
     const ReportingReport* report) const {
-  return base::ContainsKey(pending_reports_, report);
+  return base::Contains(pending_reports_, report);
 }
 
 bool ReportingCacheImpl::IsReportDoomedForTesting(
     const ReportingReport* report) const {
-  return base::ContainsKey(doomed_reports_, report);
+  return base::Contains(doomed_reports_, report);
 }
 
 void ReportingCacheImpl::OnParsedHeader(
@@ -411,7 +411,7 @@
       // Client for a superdomain of |origin|
       const OriginClient& client = client_it->second;
       // Check if |client| has a group with the requested name.
-      if (!base::ContainsKey(client.endpoint_group_names, group_name))
+      if (!base::Contains(client.endpoint_group_names, group_name))
         continue;
 
       ReportingEndpointGroupKey group_key(client.origin, group_name);
@@ -576,7 +576,7 @@
 
   for (const auto& it : reports_) {
     const ReportingReport* report = it.first;
-    if (base::ContainsKey(pending_reports_, report))
+    if (base::Contains(pending_reports_, report))
       continue;
     if (!earliest_queued || report->queued < earliest_queued->queued) {
       earliest_queued = report;
@@ -601,7 +601,7 @@
     total_endpoint_group_count += SanityCheckOriginClient(domain, client);
 
     // We have not seen a duplicate client with the same origin.
-    DCHECK(!base::ContainsKey(origins_in_cache, client.origin));
+    DCHECK(!base::Contains(origins_in_cache, client.origin));
     origins_in_cache.insert(client.origin);
   }
 
@@ -672,7 +672,7 @@
 
     // We have not seen a duplicate endpoint with the same URL in this
     // group.
-    DCHECK(!base::ContainsKey(endpoint_urls_in_group, endpoint.info.url));
+    DCHECK(!base::Contains(endpoint_urls_in_group, endpoint.info.url));
     endpoint_urls_in_group.insert(endpoint.info.url);
 
     ++endpoint_count_in_group;
@@ -693,14 +693,14 @@
   DCHECK_LE(0, endpoint.info.weight);
 
   // The endpoint is in the |endpoint_its_by_url_| index.
-  DCHECK(base::ContainsKey(endpoint_its_by_url_, endpoint.info.url));
+  DCHECK(base::Contains(endpoint_its_by_url_, endpoint.info.url));
   auto url_range = endpoint_its_by_url_.equal_range(endpoint.info.url);
   std::vector<EndpointMap::iterator> endpoint_its_for_url;
   for (auto index_it = url_range.first; index_it != url_range.second;
        ++index_it) {
     endpoint_its_for_url.push_back(index_it->second);
   }
-  DCHECK(base::ContainsValue(endpoint_its_for_url, endpoint_it));
+  DCHECK(base::Contains(endpoint_its_for_url, endpoint_it));
 #endif  // DCHECK_IS_ON()
 }
 
@@ -822,7 +822,7 @@
 
   const auto group_range = endpoints_.equal_range(group_key);
   for (auto it = group_range.first; it != group_range.second;) {
-    if (base::ContainsKey(endpoints_to_keep_urls, it->second.info.url)) {
+    if (base::Contains(endpoints_to_keep_urls, it->second.info.url)) {
       ++it;
       continue;
     }
diff --git a/net/reporting/reporting_delivery_agent.cc b/net/reporting/reporting_delivery_agent.cc
index 2745caf..31bfde7a 100644
--- a/net/reporting/reporting_delivery_agent.cc
+++ b/net/reporting/reporting_delivery_agent.cc
@@ -170,7 +170,7 @@
       const url::Origin& report_origin = origin_group.first;
       const std::string& group = origin_group.second;
 
-      if (base::ContainsKey(pending_origin_groups_, origin_group))
+      if (base::Contains(pending_origin_groups_, origin_group))
         continue;
 
       const ReportingEndpoint endpoint =
diff --git a/net/reporting/reporting_endpoint_manager.cc b/net/reporting/reporting_endpoint_manager.cc
index ec6f3863..ed3452d 100644
--- a/net/reporting/reporting_endpoint_manager.cc
+++ b/net/reporting/reporting_endpoint_manager.cc
@@ -50,7 +50,7 @@
     int total_weight = 0;
 
     for (const ReportingEndpoint endpoint : endpoints) {
-      if (base::ContainsKey(endpoint_backoff_, endpoint.info.url) &&
+      if (base::Contains(endpoint_backoff_, endpoint.info.url) &&
           endpoint_backoff_[endpoint.info.url]->ShouldRejectRequest()) {
         continue;
       }
@@ -102,7 +102,7 @@
   }
 
   void InformOfEndpointRequest(const GURL& endpoint, bool succeeded) override {
-    if (!base::ContainsKey(endpoint_backoff_, endpoint)) {
+    if (!base::Contains(endpoint_backoff_, endpoint)) {
       endpoint_backoff_[endpoint] = std::make_unique<BackoffEntry>(
           &policy().endpoint_backoff_policy, tick_clock());
     }
diff --git a/net/socket/transport_client_socket_pool.cc b/net/socket/transport_client_socket_pool.cc
index 3a1fb846..a69add8a5 100644
--- a/net/socket/transport_client_socket_pool.cc
+++ b/net/socket/transport_client_socket_pool.cc
@@ -225,14 +225,14 @@
 void TransportClientSocketPool::AddHigherLayeredPool(
     HigherLayeredPool* higher_pool) {
   CHECK(higher_pool);
-  CHECK(!base::ContainsKey(higher_pools_, higher_pool));
+  CHECK(!base::Contains(higher_pools_, higher_pool));
   higher_pools_.insert(higher_pool);
 }
 
 void TransportClientSocketPool::RemoveHigherLayeredPool(
     HigherLayeredPool* higher_pool) {
   CHECK(higher_pool);
-  CHECK(base::ContainsKey(higher_pools_, higher_pool));
+  CHECK(base::Contains(higher_pools_, higher_pool));
   higher_pools_.erase(higher_pool);
 }
 
@@ -329,11 +329,11 @@
     rv = RequestSocketInternal(group_id, request);
     if (rv < 0 && rv != ERR_IO_PENDING) {
       // We're encountering a synchronous error.  Give up.
-      if (!base::ContainsKey(group_map_, group_id))
+      if (!base::Contains(group_map_, group_id))
         deleted_group = true;
       break;
     }
-    if (!base::ContainsKey(group_map_, group_id)) {
+    if (!base::Contains(group_map_, group_id)) {
       // Unexpected.  The group should only be getting deleted on synchronous
       // error.
       NOTREACHED();
@@ -537,7 +537,7 @@
                                             RequestPriority priority) {
   auto group_it = group_map_.find(group_id);
   if (group_it == group_map_.end()) {
-    DCHECK(base::ContainsKey(pending_callback_map_, handle));
+    DCHECK(base::Contains(pending_callback_map_, handle));
     // The Request has already completed and been destroyed; nothing to
     // reprioritize.
     return;
@@ -570,7 +570,7 @@
     return;
   }
 
-  CHECK(base::ContainsKey(group_map_, group_id));
+  CHECK(base::Contains(group_map_, group_id));
   Group* group = GetOrCreateGroup(group_id);
 
   std::unique_ptr<Request> request = group->FindAndRemoveBoundRequest(handle);
@@ -633,7 +633,7 @@
 LoadState TransportClientSocketPool::GetLoadState(
     const GroupId& group_id,
     const ClientSocketHandle* handle) const {
-  if (base::ContainsKey(pending_callback_map_, handle))
+  if (base::Contains(pending_callback_map_, handle))
     return LOAD_STATE_CONNECTING;
 
   auto group_it = group_map_.find(group_id);
@@ -798,7 +798,7 @@
 }
 
 bool TransportClientSocketPool::HasGroup(const GroupId& group_id) const {
-  return base::ContainsKey(group_map_, group_id);
+  return base::Contains(group_map_, group_id);
 }
 
 void TransportClientSocketPool::CleanupIdleSockets(bool force) {
@@ -1023,7 +1023,7 @@
 
 void TransportClientSocketPool::OnAvailableSocketSlot(const GroupId& group_id,
                                                       Group* group) {
-  DCHECK(base::ContainsKey(group_map_, group_id));
+  DCHECK(base::Contains(group_map_, group_id));
   if (group->IsEmpty()) {
     RemoveGroup(group_id);
   } else if (group->has_unbound_requests()) {
@@ -1295,7 +1295,7 @@
     CompletionOnceCallback callback,
     int rv,
     const SocketTag& socket_tag) {
-  CHECK(!base::ContainsKey(pending_callback_map_, handle));
+  CHECK(!base::Contains(pending_callback_map_, handle));
   pending_callback_map_[handle] = CallbackResultPair(std::move(callback), rv);
   if (rv == OK) {
     handle->socket()->ApplySocketTag(socket_tag);
diff --git a/net/spdy/spdy_session.cc b/net/spdy/spdy_session.cc
index 6e71a07..e04bb62 100644
--- a/net/spdy/spdy_session.cc
+++ b/net/spdy/spdy_session.cc
@@ -959,12 +959,10 @@
       NetLogEventType::HTTP2_SESSION,
       base::Bind(&NetLogSpdySessionCallback, &host_port_proxy_pair()));
 
+  DCHECK(base::Contains(initial_settings_, spdy::SETTINGS_HEADER_TABLE_SIZE));
   DCHECK(
-      base::ContainsKey(initial_settings_, spdy::SETTINGS_HEADER_TABLE_SIZE));
-  DCHECK(base::ContainsKey(initial_settings_,
-                           spdy::SETTINGS_MAX_CONCURRENT_STREAMS));
-  DCHECK(
-      base::ContainsKey(initial_settings_, spdy::SETTINGS_INITIAL_WINDOW_SIZE));
+      base::Contains(initial_settings_, spdy::SETTINGS_MAX_CONCURRENT_STREAMS));
+  DCHECK(base::Contains(initial_settings_, spdy::SETTINGS_INITIAL_WINDOW_SIZE));
 
   if (greased_http2_frame_) {
     // See https://tools.ietf.org/html/draft-bishop-httpbis-grease-00
@@ -1335,7 +1333,7 @@
 }
 
 bool SpdySession::IsStreamActive(spdy::SpdyStreamId stream_id) const {
-  return base::ContainsKey(active_streams_, stream_id);
+  return base::Contains(active_streams_, stream_id);
 }
 
 LoadState SpdySession::GetLoadState() const {
diff --git a/net/spdy/spdy_session_pool.cc b/net/spdy/spdy_session_pool.cc
index 0d527b6..924c40c1a 100644
--- a/net/spdy/spdy_session_pool.cc
+++ b/net/spdy/spdy_session_pool.cc
@@ -496,7 +496,7 @@
                        weak_ptr_factory_.GetWeakPtr(), request->key()));
   }
 
-  DCHECK(base::ContainsKey(iter->second.request_set, request));
+  DCHECK(base::Contains(iter->second.request_set, request));
   RemoveRequestInternal(iter, iter->second.request_set.find(request));
 }
 
@@ -563,7 +563,7 @@
 void SpdySessionPool::MapKeyToAvailableSession(
     const SpdySessionKey& key,
     const base::WeakPtr<SpdySession>& session) {
-  DCHECK(base::ContainsKey(sessions_, session.get()));
+  DCHECK(base::Contains(sessions_, session.get()));
   std::pair<AvailableSessionMap::iterator, bool> result =
       available_sessions_.insert(std::make_pair(key, session));
   CHECK(result.second);
diff --git a/net/tools/quic/quic_http_proxy_backend_stream.cc b/net/tools/quic/quic_http_proxy_backend_stream.cc
index b247cf6..9aecf83 100644
--- a/net/tools/quic/quic_http_proxy_backend_stream.cc
+++ b/net/tools/quic/quic_http_proxy_backend_stream.cc
@@ -147,7 +147,7 @@
     // Ignore the spdy headers
     if (!key.empty() && key[0] != ':') {
       // Remove hop-by-hop headers
-      if (base::ContainsKey(kHopHeaders, key)) {
+      if (base::Contains(kHopHeaders, key)) {
         LOG(INFO) << "QUIC Proxy Ignoring Hop-by-hop Request Header: " << key
                   << ":" << value;
       } else {
@@ -371,7 +371,7 @@
       if (header_name.compare("status") != 0) {
         if (header_name.compare("content-encoding") != 0) {
           // Remove hop-by-hop headers
-          if (base::ContainsKey(kHopHeaders, header_name)) {
+          if (base::Contains(kHopHeaders, header_name)) {
             LOG(INFO) << "Quic Proxy Ignoring Hop-by-hop Response Header: "
                       << header_name << ":" << header_value;
           } else {
diff --git a/net/url_request/url_fetcher_core.cc b/net/url_request/url_fetcher_core.cc
index f805da8..a6c1740 100644
--- a/net/url_request/url_fetcher_core.cc
+++ b/net/url_request/url_fetcher_core.cc
@@ -47,12 +47,12 @@
 URLFetcherCore::Registry::~Registry() = default;
 
 void URLFetcherCore::Registry::AddURLFetcherCore(URLFetcherCore* core) {
-  DCHECK(!base::ContainsKey(fetchers_, core));
+  DCHECK(!base::Contains(fetchers_, core));
   fetchers_.insert(core);
 }
 
 void URLFetcherCore::Registry::RemoveURLFetcherCore(URLFetcherCore* core) {
-  DCHECK(base::ContainsKey(fetchers_, core));
+  DCHECK(base::Contains(fetchers_, core));
   fetchers_.erase(core);
 }
 
diff --git a/net/url_request/url_request_job_factory_impl.cc b/net/url_request/url_request_job_factory_impl.cc
index c98a0b24..ad38c62 100644
--- a/net/url_request/url_request_job_factory_impl.cc
+++ b/net/url_request/url_request_job_factory_impl.cc
@@ -36,7 +36,7 @@
     return true;
   }
 
-  if (base::ContainsKey(protocol_handler_map_, scheme))
+  if (base::Contains(protocol_handler_map_, scheme))
     return false;
   protocol_handler_map_[scheme] = std::move(protocol_handler);
   return true;
@@ -76,7 +76,7 @@
 bool URLRequestJobFactoryImpl::IsHandledProtocol(
     const std::string& scheme) const {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  return base::ContainsKey(protocol_handler_map_, scheme) ||
+  return base::Contains(protocol_handler_map_, scheme) ||
          URLRequestJobManager::GetInstance()->SupportsScheme(scheme);
 }
 
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc
index 4646cf10..ecea7cb 100644
--- a/net/url_request/url_request_unittest.cc
+++ b/net/url_request/url_request_unittest.cc
@@ -8060,6 +8060,8 @@
 
   // If the feature isn't enabled, a long `referer` will remain long.
   TestDelegate d;
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndDisableFeature(features::kCapRefererHeaderLength);
   std::unique_ptr<URLRequest> req(default_context().CreateRequest(
       http_test_server()->GetURL("/echoheader?Referer"), DEFAULT_PRIORITY, &d,
       TRAFFIC_ANNOTATION_FOR_TESTS));
diff --git a/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.cc b/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.cc
index e2bc055..7f8ac8e 100644
--- a/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.cc
+++ b/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.cc
@@ -7,7 +7,6 @@
 #include "base/stl_util.h"
 #include "base/strings/pattern.h"
 #include "base/strings/strcat.h"
-#include "base/threading/thread_id_name_manager.h"
 #include "base/trace_event/common/trace_event_common.h"
 #include "base/trace_event/trace_buffer.h"
 #include "base/trace_event/trace_log.h"
@@ -170,9 +169,13 @@
       interned_source_locations_(1000),
       process_id_(TraceLog::GetInstance()->process_id()),
       thread_id_(static_cast<int>(base::PlatformThread::CurrentId())),
-      privacy_filtering_enabled_(proto_writer_filtering_enabled) {}
+      privacy_filtering_enabled_(proto_writer_filtering_enabled) {
+  base::ThreadIdNameManager::GetInstance()->AddObserver(this);
+}
 
-TrackEventThreadLocalEventSink::~TrackEventThreadLocalEventSink() {}
+TrackEventThreadLocalEventSink::~TrackEventThreadLocalEventSink() {
+  base::ThreadIdNameManager::GetInstance()->RemoveObserver(this);
+}
 
 // static
 void TrackEventThreadLocalEventSink::ClearIncrementalState() {
@@ -503,25 +506,29 @@
   trace_writer_->Flush();
 }
 
-void TrackEventThreadLocalEventSink::DoResetIncrementalState(
-    base::trace_event::TraceEvent* trace_event,
-    bool explicit_timestamp) {
-  interned_event_categories_.ResetEmittedState();
-  interned_event_names_.ResetEmittedState();
-  interned_annotation_names_.ResetEmittedState();
-  interned_source_locations_.ResetEmittedState();
-
-  // Emit a new thread descriptor in a separate packet, where we also set
-  // the |reset_incremental_state| field.
+void TrackEventThreadLocalEventSink::OnThreadNameChanged(const char* name) {
+  if (thread_id_ != static_cast<int>(base::PlatformThread::CurrentId()))
+    return;
   auto trace_packet = trace_writer_->NewTracePacket();
-  trace_packet->set_incremental_state_cleared(true);
-  auto* thread_descriptor = trace_packet->set_thread_descriptor();
+  EmitThreadDescriptor(&trace_packet, nullptr, true, name);
+}
+
+void TrackEventThreadLocalEventSink::EmitThreadDescriptor(
+    protozero::MessageHandle<perfetto::protos::pbzero::TracePacket>*
+        trace_packet,
+    base::trace_event::TraceEvent* trace_event,
+    bool explicit_timestamp,
+    const char* maybe_new_name) {
+  auto* thread_descriptor = (*trace_packet)->set_thread_descriptor();
   thread_descriptor->set_pid(process_id_);
   thread_descriptor->set_tid(thread_id_);
 
-  const char* const maybe_new_name =
-      base::ThreadIdNameManager::GetInstance()->GetName(thread_id_);
-  if (maybe_new_name && base::StringPiece(thread_name_) != maybe_new_name) {
+  if (!maybe_new_name) {
+    maybe_new_name =
+        base::ThreadIdNameManager::GetInstance()->GetNameForCurrentThread();
+  }
+  if (maybe_new_name && *maybe_new_name &&
+      base::StringPiece(thread_name_) != maybe_new_name) {
     thread_name_ = maybe_new_name;
     thread_type_ = GetThreadType(maybe_new_name);
   }
@@ -530,13 +537,13 @@
   }
   thread_descriptor->set_chrome_thread_type(thread_type_);
 
-  if (explicit_timestamp) {
+  if (explicit_timestamp || !trace_event) {
     // Don't use a user-provided timestamp as a reference timestamp.
     last_timestamp_ = TRACE_TIME_TICKS_NOW();
   } else {
     last_timestamp_ = trace_event->timestamp();
   }
-  if (trace_event->thread_timestamp().is_null()) {
+  if (!trace_event || trace_event->thread_timestamp().is_null()) {
     last_thread_time_ = ThreadNow();
   } else {
     // Thread timestamp is never user-provided.
@@ -548,7 +555,21 @@
   thread_descriptor->set_reference_thread_time_us(
       last_thread_time_.since_origin().InMicroseconds());
   // TODO(eseckler): Fill in remaining fields in ThreadDescriptor.
+}
 
+void TrackEventThreadLocalEventSink::DoResetIncrementalState(
+    base::trace_event::TraceEvent* trace_event,
+    bool explicit_timestamp) {
+  interned_event_categories_.ResetEmittedState();
+  interned_event_names_.ResetEmittedState();
+  interned_annotation_names_.ResetEmittedState();
+  interned_source_locations_.ResetEmittedState();
+
+  // Emit a new thread descriptor in a separate packet, where we also set
+  // the |incremental_state_cleared| flag.
+  auto trace_packet = trace_writer_->NewTracePacket();
+  trace_packet->set_incremental_state_cleared(true);
+  EmitThreadDescriptor(&trace_packet, trace_event, explicit_timestamp);
   reset_incremental_state_ = false;
 }
 
diff --git a/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.h b/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.h
index b672375..5519857 100644
--- a/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.h
+++ b/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.h
@@ -11,6 +11,7 @@
 #include <utility>
 
 #include "base/component_export.h"
+#include "base/threading/thread_id_name_manager.h"
 #include "base/time/time.h"
 #include "services/tracing/public/cpp/perfetto/interning_index.h"
 #include "services/tracing/public/cpp/perfetto/thread_local_event_sink.h"
@@ -26,7 +27,8 @@
 
 // ThreadLocalEventSink that emits TrackEvent protos.
 class COMPONENT_EXPORT(TRACING_CPP) TrackEventThreadLocalEventSink
-    : public ThreadLocalEventSink {
+    : public ThreadLocalEventSink,
+      public base::ThreadIdNameManager::Observer {
  public:
   TrackEventThreadLocalEventSink(
       std::unique_ptr<perfetto::StartupTraceWriter> trace_writer,
@@ -47,9 +49,18 @@
                       const base::ThreadTicks& thread_now) override;
   void Flush() override;
 
+  // ThreadIdNameManager::Observer implementation:
+  void OnThreadNameChanged(const char* name) override;
+
  private:
   static constexpr size_t kMaxCompleteEventDepth = 30;
 
+  void EmitThreadDescriptor(
+      protozero::MessageHandle<perfetto::protos::pbzero::TracePacket>*
+          trace_packet,
+      base::trace_event::TraceEvent* trace_event,
+      bool explicit_timestamp,
+      const char* maybe_new_name = nullptr);
   void DoResetIncrementalState(base::trace_event::TraceEvent* trace_event,
                                bool explicit_timestamp);
 
diff --git a/services/viz/public/interfaces/compositing/compositor_frame_sink.mojom b/services/viz/public/interfaces/compositing/compositor_frame_sink.mojom
index 4e91f18..2c767f91 100644
--- a/services/viz/public/interfaces/compositing/compositor_frame_sink.mojom
+++ b/services/viz/public/interfaces/compositing/compositor_frame_sink.mojom
@@ -5,6 +5,7 @@
 module viz.mojom;
 
 import "mojo/public/mojom/base/time.mojom";
+import "mojo/public/mojom/base/shared_memory.mojom";
 import "services/viz/public/interfaces/compositing/begin_frame_args.mojom";
 import "services/viz/public/interfaces/compositing/compositor_frame.mojom";
 import "services/viz/public/interfaces/compositing/local_surface_id.mojom";
@@ -67,7 +68,8 @@
   // The |id| can then be used in subsequent CompositorFrames given to
   // SubmitCompositorFrame. The |id| is a Mailbox type which doubles as a
   // SharedBitmapId for this case.
-  DidAllocateSharedBitmap(handle<shared_buffer> buffer, gpu.mojom.Mailbox id);
+  DidAllocateSharedBitmap(mojo_base.mojom.ReadOnlySharedMemoryRegion region,
+                          gpu.mojom.Mailbox id);
 
   // Informs the display compositor that the client deleted a shared bitmap. This
   // allows the service to free the shared memory that was previously given to it
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 00a4ab24..cb4ff6b0 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -1027,6 +1027,25 @@
             ]
         }
     ],
+    "AutofillUseMobileLabelDisambiguation": [
+        {
+            "platforms": [
+                "android",
+                "ios"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled_ShowAll",
+                    "params": {
+                        "variant": "show-all"
+                    },
+                    "enable_features": [
+                        "AutofillUseMobileLabelDisambiguation"
+                    ]
+                }
+            ]
+        }
+    ],
     "AutomaticTabDiscarding": [
         {
             "platforms": [
diff --git a/third_party/blink/public/BUILD.gn b/third_party/blink/public/BUILD.gn
index bacd5d5..57c25c1 100644
--- a/third_party/blink/public/BUILD.gn
+++ b/third_party/blink/public/BUILD.gn
@@ -148,6 +148,7 @@
     "platform/modules/mediastream/web_media_stream_video_renderer.h",
     "platform/modules/mediastream/web_platform_media_stream_source.h",
     "platform/modules/mediastream/web_platform_media_stream_track.h",
+    "platform/modules/mediastream/webaudio_media_stream_source.h",
     "platform/modules/mediastream/webrtc_uma_histograms.h",
     "platform/modules/push_messaging/web_push_error.h",
     "platform/modules/push_messaging/web_push_subscription.h",
diff --git a/content/renderer/media/stream/webaudio_media_stream_source.h b/third_party/blink/public/platform/modules/mediastream/webaudio_media_stream_source.h
similarity index 78%
rename from content/renderer/media/stream/webaudio_media_stream_source.h
rename to third_party/blink/public/platform/modules/mediastream/webaudio_media_stream_source.h
index 99ab9b4..ace9be0 100644
--- a/content/renderer/media/stream/webaudio_media_stream_source.h
+++ b/third_party/blink/public/platform/modules/mediastream/webaudio_media_stream_source.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_RENDERER_MEDIA_STREAM_WEBAUDIO_MEDIA_STREAM_SOURCE_H_
-#define CONTENT_RENDERER_MEDIA_STREAM_WEBAUDIO_MEDIA_STREAM_SOURCE_H_
+#ifndef THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIASTREAM_WEBAUDIO_MEDIA_STREAM_SOURCE_H_
+#define THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIASTREAM_WEBAUDIO_MEDIA_STREAM_SOURCE_H_
 
 #include <memory>
 
@@ -12,21 +12,22 @@
 #include "media/base/audio_push_fifo.h"
 #include "third_party/blink/public/platform/modules/mediastream/media_stream_audio_source.h"
 #include "third_party/blink/public/platform/web_audio_destination_consumer.h"
+#include "third_party/blink/public/platform/web_common.h"
 #include "third_party/blink/public/platform/web_media_stream_source.h"
 #include "third_party/blink/public/platform/web_vector.h"
 
-namespace content {
+namespace blink {
 
 // Implements the WebAudioDestinationConsumer interface to provide a source of
 // audio data (i.e., the output from a graph of WebAudio nodes) to one or more
 // MediaStreamAudioTracks. Audio data is transported directly to the tracks in
 // 10 ms chunks.
-class WebAudioMediaStreamSource final
-    : public blink::MediaStreamAudioSource,
-      public blink::WebAudioDestinationConsumer {
+class BLINK_PLATFORM_EXPORT WebAudioMediaStreamSource final
+    : public MediaStreamAudioSource,
+      public WebAudioDestinationConsumer {
  public:
   WebAudioMediaStreamSource(
-      blink::WebMediaStreamSource* blink_source,
+      WebMediaStreamSource* blink_source,
       scoped_refptr<base::SingleThreadTaskRunner> task_runner);
 
   ~WebAudioMediaStreamSource() override;
@@ -38,7 +39,7 @@
   // concurrently across threads, but these methods could be called on any
   // thread.
   void SetFormat(size_t number_of_channels, float sample_rate) override;
-  void ConsumeAudio(const blink::WebVector<const float*>& audio_data,
+  void ConsumeAudio(const WebVector<const float*>& audio_data,
                     size_t number_of_frames) override;
 
   // Called by AudioPushFifo zero or more times during the call to
@@ -72,13 +73,13 @@
   // DeliverRebufferedAudio().
   base::TimeTicks current_reference_time_;
 
-  // This object registers with a blink::WebMediaStreamSource. We keep track of
+  // This object registers with a WebMediaStreamSource. We keep track of
   // that in order to be able to deregister before stopping this source.
-  blink::WebMediaStreamSource blink_source_;
+  WebMediaStreamSource blink_source_;
 
   DISALLOW_COPY_AND_ASSIGN(WebAudioMediaStreamSource);
 };
 
-}  // namespace content
+}  // namespace blink
 
-#endif  // CONTENT_RENDERER_MEDIA_STREAM_WEBAUDIO_MEDIA_STREAM_SOURCE_H_
+#endif  // THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIASTREAM_WEBAUDIO_MEDIA_STREAM_SOURCE_H_
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn
index c7b31b0..5728950 100644
--- a/third_party/blink/renderer/core/BUILD.gn
+++ b/third_party/blink/renderer/core/BUILD.gn
@@ -1868,7 +1868,6 @@
     "html/custom/custom_element_upgrade_sorter_test.cc",
     "html/forms/email_input_type_test.cc",
     "html/forms/external_popup_menu_test.cc",
-    "html/forms/file_chooser_test.cc",
     "html/forms/file_input_type_test.cc",
     "html/forms/form_controller_test.cc",
     "html/forms/form_data_test.cc",
@@ -2154,6 +2153,7 @@
     "streams/readable_stream_test.cc",
     "streams/stream_promise_resolver_test.cc",
     "streams/test_underlying_source.h",
+    "streams/transferable_streams_test.cc",
     "streams/transform_stream_test.cc",
     "streams/writable_stream_test.cc",
     "style/border_value_test.cc",
diff --git a/third_party/blink/renderer/core/html/forms/file_chooser.h b/third_party/blink/renderer/core/html/forms/file_chooser.h
index 594ee1c..1042c13f5 100644
--- a/third_party/blink/renderer/core/html/forms/file_chooser.h
+++ b/third_party/blink/renderer/core/html/forms/file_chooser.h
@@ -96,7 +96,7 @@
   // mojom::blink::FileChooser callback
   void DidChooseFiles(mojom::blink::FileChooserResultPtr result);
 
-  Persistent<FileChooserClient> client_;
+  WeakPersistent<FileChooserClient> client_;
   mojom::blink::FileChooserParamsPtr params_;
   Persistent<ChromeClientImpl> chrome_client_impl_;
   mojom::blink::FileChooserPtr file_chooser_;
diff --git a/third_party/blink/renderer/core/html/forms/file_chooser_test.cc b/third_party/blink/renderer/core/html/forms/file_chooser_test.cc
deleted file mode 100644
index 321cd9f..0000000
--- a/third_party/blink/renderer/core/html/forms/file_chooser_test.cc
+++ /dev/null
@@ -1,33 +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 "third_party/blink/renderer/core/html/forms/file_chooser.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
-#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
-#include "third_party/blink/renderer/core/html/forms/mock_file_chooser.h"
-#include "third_party/blink/renderer/core/page/chrome_client_impl.h"
-
-namespace blink {
-
-class FileChooserTest : public testing::Test {
- protected:
-  void SetUp() override { web_view_ = helper_.Initialize(); }
-
-  frame_test_helpers::WebViewHelper helper_;
-  WebViewImpl* web_view_;
-};
-
-TEST_F(FileChooserTest, NotGarbageCollected) {
-  LocalFrame* frame = helper_.LocalMainFrame()->GetFrame();
-  auto* client = MakeGarbageCollected<MockFileChooserClient>(frame);
-  mojom::blink::FileChooserParams params;
-  params.title = g_empty_string;
-  scoped_refptr<FileChooser> chooser = FileChooser::Create(client, params);
-  ThreadState::Current()->CollectAllGarbageForTesting();
-  // This tests the nullness of FileChooser::client_.
-  EXPECT_TRUE(chooser->FrameOrNull());
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/core/html/forms/mock_file_chooser.h b/third_party/blink/renderer/core/html/forms/mock_file_chooser.h
index ff28ea1..2058d12 100644
--- a/third_party/blink/renderer/core/html/forms/mock_file_chooser.h
+++ b/third_party/blink/renderer/core/html/forms/mock_file_chooser.h
@@ -74,28 +74,5 @@
   base::OnceClosure reached_callback_;
 };
 
-// A FileChooserClient which makes FileChooser::OpenFileChooser() success.
-class MockFileChooserClient
-    : public GarbageCollectedFinalized<MockFileChooserClient>,
-      public FileChooserClient {
-  USING_GARBAGE_COLLECTED_MIXIN(MockFileChooserClient);
-
- public:
-  explicit MockFileChooserClient(LocalFrame* frame) : frame_(frame) {}
-  void Trace(Visitor* visitor) override {
-    visitor->Trace(frame_);
-    FileChooserClient::Trace(visitor);
-  }
-
- private:
-  // FilesChosen() and WillOpenPopup() are never called in the test.
-  void FilesChosen(FileChooserFileInfoList, const base::FilePath&) override {}
-  void WillOpenPopup() override {}
-
-  LocalFrame* FrameOrNull() const override { return frame_; }
-
-  Member<LocalFrame> frame_;
-};
-
 }  // namespace blink
 #endif  // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_FORMS_MOCK_FILE_CHOOSER_H_
diff --git a/third_party/blink/renderer/core/layout/README.md b/third_party/blink/renderer/core/layout/README.md
index 7b2723b..e9add9f 100644
--- a/third_party/blink/renderer/core/layout/README.md
+++ b/third_party/blink/renderer/core/layout/README.md
@@ -150,8 +150,8 @@
 
 ## Coordinate Spaces
 
-Layout and Paint work with and frequently refer to three main coordinate spaces
-(really two, with one variant):
+Layout and Paint work with and frequently refer to four coordinate spaces
+(really two, with two variants):
 
 * Physical coordinates: Corresponds to physical direction of the output per the
   physical display (screen, printed page). Generally used for painting, thus
@@ -180,6 +180,10 @@
   look up the writing mode of an object.  Performing computation on values known
   to be in this space can save on the overhead required to unflip/reflip.
 
+* Logical coordinates without flipping inline direction: those are "logical
+  block coordinates", without considering text direction. Examples are
+  "LogicalLeft" and "LogicalRight".
+
 Example with `writing-mode: vertical-rl; direction: ltr`:
 
                        'top' / 'start' side
diff --git a/third_party/blink/renderer/core/layout/layout_block_flow.cc b/third_party/blink/renderer/core/layout/layout_block_flow.cc
index de67530..ad4c52d 100644
--- a/third_party/blink/renderer/core/layout/layout_block_flow.cc
+++ b/third_party/blink/renderer/core/layout/layout_block_flow.cc
@@ -4533,7 +4533,7 @@
 
 void LayoutBlockFlow::SimplifiedNormalFlowInlineLayout() {
   DCHECK(ChildrenInline());
-  ListHashSet<RootInlineBox*> line_boxes;
+  LinkedHashSet<RootInlineBox*> line_boxes;
   for (InlineWalker walker(LineLayoutBlockFlow(this)); !walker.AtEnd();
        walker.Advance()) {
     LayoutObject* o = walker.Current().GetLayoutObject();
@@ -4553,7 +4553,7 @@
   // FIXME: Glyph overflow will get lost in this case, but not really a big
   // deal.
   GlyphOverflowAndFallbackFontsMap text_box_data_map;
-  for (ListHashSet<RootInlineBox*>::const_iterator it = line_boxes.begin();
+  for (LinkedHashSet<RootInlineBox*>::const_iterator it = line_boxes.begin();
        it != line_boxes.end(); ++it) {
     RootInlineBox* box = *it;
     box->ComputeOverflow(box->LineTop(), box->LineBottom(), text_box_data_map);
@@ -4563,7 +4563,7 @@
 bool LayoutBlockFlow::RecalcInlineChildrenLayoutOverflow() {
   DCHECK(ChildrenInline());
   bool children_layout_overflow_changed = false;
-  ListHashSet<RootInlineBox*> line_boxes;
+  LinkedHashSet<RootInlineBox*> line_boxes;
   for (InlineWalker walker(LineLayoutBlockFlow(this)); !walker.AtEnd();
        walker.Advance()) {
     LayoutObject* layout_object = walker.Current().GetLayoutObject();
@@ -4583,7 +4583,7 @@
   // FIXME: Glyph overflow will get lost in this case, but not really a big
   // deal.
   GlyphOverflowAndFallbackFontsMap text_box_data_map;
-  for (ListHashSet<RootInlineBox*>::const_iterator it = line_boxes.begin();
+  for (LinkedHashSet<RootInlineBox*>::const_iterator it = line_boxes.begin();
        it != line_boxes.end(); ++it) {
     RootInlineBox* box = *it;
     box->ClearKnownToHaveNoOverflow();
diff --git a/third_party/blink/renderer/core/layout/layout_box_model_object.h b/third_party/blink/renderer/core/layout/layout_box_model_object.h
index 23e65eb6..c2488d7 100644
--- a/third_party/blink/renderer/core/layout/layout_box_model_object.h
+++ b/third_party/blink/renderer/core/layout/layout_box_model_object.h
@@ -79,7 +79,7 @@
 //
 // In order to fully understand LayoutBoxModelObject and the inherited classes,
 // we need to introduce the concept of coordinate systems.
-// There is 3 main coordinate systems:
+// There are 4 coordinate systems:
 // - physical coordinates: it is the coordinate system used for painting and
 //   correspond to physical direction as seen on the physical display (screen,
 //   printed page). In CSS, 'top', 'right', 'bottom', 'left' are all in physical
@@ -119,6 +119,10 @@
 //   (e.g. InlineStart, BlockEnd) or physical (e.g. Top, Left), or the return
 //   type is PhysicalRect.
 //
+// - logical coordinates without flipping inline direction: those are "logical
+//   block coordinates", without considering text direction. Examples are
+//   "LogicalLeft" and "LogicalRight".
+//
 // For more, see Source/core/layout/README.md ### Coordinate Spaces.
 class CORE_EXPORT LayoutBoxModelObject : public LayoutObject {
  public:
diff --git a/third_party/blink/renderer/core/page/chrome_client_impl_test.cc b/third_party/blink/renderer/core/page/chrome_client_impl_test.cc
index 2154985..61bc163 100644
--- a/third_party/blink/renderer/core/page/chrome_client_impl_test.cc
+++ b/third_party/blink/renderer/core/page/chrome_client_impl_test.cc
@@ -230,6 +230,29 @@
   EXPECT_TRUE(CanOpenDateTimeChooser());
 }
 
+// A FileChooserClient which makes FileChooser::OpenFileChooser() success.
+class MockFileChooserClient
+    : public GarbageCollectedFinalized<MockFileChooserClient>,
+      public FileChooserClient {
+  USING_GARBAGE_COLLECTED_MIXIN(MockFileChooserClient);
+
+ public:
+  explicit MockFileChooserClient(LocalFrame* frame) : frame_(frame) {}
+  void Trace(Visitor* visitor) override {
+    visitor->Trace(frame_);
+    FileChooserClient::Trace(visitor);
+  }
+
+ private:
+  // FilesChosen() and WillOpenPopup() are never called in the test.
+  void FilesChosen(FileChooserFileInfoList, const base::FilePath&) override {}
+  void WillOpenPopup() override {}
+
+  LocalFrame* FrameOrNull() const override { return frame_; }
+
+  Member<LocalFrame> frame_;
+};
+
 class FileChooserQueueTest : public testing::Test {
  protected:
   void SetUp() override {
diff --git a/third_party/blink/renderer/core/streams/BUILD.gn b/third_party/blink/renderer/core/streams/BUILD.gn
index 24a5a05..d8c60b6 100644
--- a/third_party/blink/renderer/core/streams/BUILD.gn
+++ b/third_party/blink/renderer/core/streams/BUILD.gn
@@ -30,6 +30,8 @@
     "stream_algorithms.h",
     "stream_promise_resolver.cc",
     "stream_promise_resolver.h",
+    "transferable_streams.cc",
+    "transferable_streams.h",
     "transform_stream.cc",
     "transform_stream.h",
     "transform_stream_default_controller.cc",
diff --git a/third_party/blink/renderer/core/streams/README.md b/third_party/blink/renderer/core/streams/README.md
index 6856924..1b845c20 100644
--- a/third_party/blink/renderer/core/streams/README.md
+++ b/third_party/blink/renderer/core/streams/README.md
@@ -26,6 +26,8 @@
     writable_stream_default_writer.idl
     writable_stream_native.cc
     writable_stream_native.h
+    transferable_streams.cc
+    transferable_streams.h
     transform_stream_default_controller.cc
     transform_stream_default_controller.h
     transform_stream_native.cc
diff --git a/third_party/blink/renderer/core/streams/readable_stream.cc b/third_party/blink/renderer/core/streams/readable_stream.cc
index 8b787d6..e1088481 100644
--- a/third_party/blink/renderer/core/streams/readable_stream.cc
+++ b/third_party/blink/renderer/core/streams/readable_stream.cc
@@ -60,11 +60,9 @@
 ReadableStream* ReadableStream::Deserialize(ScriptState* script_state,
                                             MessagePort* port,
                                             ExceptionState& exception_state) {
-  // TODO(ricea): Implementation serialization for the native implementation.
   if (RuntimeEnabledFeatures::StreamsNativeEnabled()) {
-    exception_state.ThrowTypeError(
-        "serialization disabled because StreamsNative feature is enabled");
-    return nullptr;
+    return ReadableStreamNative::Deserialize(script_state, port,
+                                             exception_state);
   }
 
   return ReadableStreamWrapper::Deserialize(script_state, port,
diff --git a/third_party/blink/renderer/core/streams/readable_stream_native.cc b/third_party/blink/renderer/core/streams/readable_stream_native.cc
index d957c75..1828317 100644
--- a/third_party/blink/renderer/core/streams/readable_stream_native.cc
+++ b/third_party/blink/renderer/core/streams/readable_stream_native.cc
@@ -15,6 +15,7 @@
 #include "third_party/blink/renderer/core/streams/readable_stream_reader.h"
 #include "third_party/blink/renderer/core/streams/stream_algorithms.h"
 #include "third_party/blink/renderer/core/streams/stream_promise_resolver.h"
+#include "third_party/blink/renderer/core/streams/transferable_streams.h"
 #include "third_party/blink/renderer/core/streams/underlying_source_base.h"
 #include "third_party/blink/renderer/core/streams/writable_stream_default_controller.h"
 #include "third_party/blink/renderer/core/streams/writable_stream_default_writer.h"
@@ -30,13 +31,6 @@
 
 namespace blink {
 
-struct ReadableStreamNative::PipeOptions {
-  PipeOptions() = default;
-  bool prevent_close = false;
-  bool prevent_abort = false;
-  bool prevent_cancel = false;
-};
-
 // PipeToEngine implements PipeTo(). All standard steps in this class come from
 // https://streams.spec.whatwg.org/#readable-stream-pipe-to
 //
@@ -1394,7 +1388,43 @@
 void ReadableStreamNative::Serialize(ScriptState* script_state,
                                      MessagePort* port,
                                      ExceptionState& exception_state) {
-  // TODO(ricea): Implement this.
+  if (IsLocked(this)) {
+    exception_state.ThrowTypeError("Cannot transfer a locked stream");
+    return;
+  }
+
+  auto* writable =
+      CreateCrossRealmTransformWritable(script_state, port, exception_state);
+  if (exception_state.HadException()) {
+    return;
+  }
+
+  auto promise = PipeTo(script_state, this, writable, PipeOptions());
+  promise.MarkAsHandled();
+}
+
+ReadableStreamNative* ReadableStreamNative::Deserialize(
+    ScriptState* script_state,
+    MessagePort* port,
+    ExceptionState& exception_state) {
+  // We need to execute JavaScript to call "Then" on v8::Promises. We will not
+  // run author code.
+  v8::Isolate::AllowJavascriptExecutionScope allow_js(
+      script_state->GetIsolate());
+  auto* readable =
+      CreateCrossRealmTransformReadable(script_state, port, exception_state);
+  if (exception_state.HadException()) {
+    return nullptr;
+  }
+  return readable;
+}
+
+ScriptPromise ReadableStreamNative::PipeTo(ScriptState* script_state,
+                                           ReadableStreamNative* readable,
+                                           WritableStreamNative* destination,
+                                           PipeOptions pipe_options) {
+  auto* engine = MakeGarbageCollected<PipeToEngine>(script_state, pipe_options);
+  return engine->Start(readable, destination);
 }
 
 v8::Local<v8::Value> ReadableStreamNative::GetStoredError(
@@ -1409,14 +1439,6 @@
   ReadableStream::Trace(visitor);
 }
 
-ScriptPromise ReadableStreamNative::PipeTo(ScriptState* script_state,
-                                           ReadableStreamNative* readable,
-                                           WritableStreamNative* destination,
-                                           PipeOptions pipe_options) {
-  auto* engine = MakeGarbageCollected<PipeToEngine>(script_state, pipe_options);
-  return engine->Start(readable, destination);
-}
-
 //
 // Abstract Operations Used By Controllers
 //
diff --git a/third_party/blink/renderer/core/streams/readable_stream_native.h b/third_party/blink/renderer/core/streams/readable_stream_native.h
index b2af17d4..9cd1fea 100644
--- a/third_party/blink/renderer/core/streams/readable_stream_native.h
+++ b/third_party/blink/renderer/core/streams/readable_stream_native.h
@@ -30,6 +30,13 @@
 // See https://streams.spec.whatwg.org/#rs-model for background.
 class ReadableStreamNative : public ReadableStream {
  public:
+  struct PipeOptions {
+    PipeOptions() = default;
+    bool prevent_close = false;
+    bool prevent_abort = false;
+    bool prevent_cancel = false;
+  };
+
   enum State : uint8_t { kReadable, kClosed, kErrored };
 
   // Implements ReadableStream::Create() when this implementation is enabled.
@@ -146,6 +153,10 @@
 
   void Serialize(ScriptState*, MessagePort* port, ExceptionState&) override;
 
+  static ReadableStreamNative* Deserialize(ScriptState*,
+                                           MessagePort* port,
+                                           ExceptionState&);
+
   bool IsBroken() const override { return false; }
 
   //
@@ -162,6 +173,12 @@
     return stream->reader_;
   }
 
+  // https://streams.spec.whatwg.org/#readable-stream-pipe-to
+  static ScriptPromise PipeTo(ScriptState*,
+                              ReadableStreamNative*,
+                              WritableStreamNative*,
+                              PipeOptions);
+
   //
   // Functions exported for use by TransformStream. Not part of the standard.
   //
@@ -190,7 +207,6 @@
   friend class ReadableStreamDefaultController;
   friend class ReadableStreamReader;
 
-  struct PipeOptions;
   class PipeToEngine;
   class ReadHandleImpl;
   class TeeEngine;
@@ -204,12 +220,6 @@
                                                     bool for_author_code,
                                                     ExceptionState&);
 
-  // https://streams.spec.whatwg.org/#readable-stream-pipe-to
-  static ScriptPromise PipeTo(ScriptState*,
-                              ReadableStreamNative*,
-                              WritableStreamNative*,
-                              PipeOptions);
-
   // https://streams.spec.whatwg.org/#readable-stream-add-read-request
   static StreamPromiseResolver* AddReadRequest(ScriptState*,
                                                ReadableStreamNative*);
diff --git a/third_party/blink/renderer/core/streams/readable_stream_reader.h b/third_party/blink/renderer/core/streams/readable_stream_reader.h
index 3d5f8d3..6099dca 100644
--- a/third_party/blink/renderer/core/streams/readable_stream_reader.h
+++ b/third_party/blink/renderer/core/streams/readable_stream_reader.h
@@ -6,6 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_STREAMS_READABLE_STREAM_READER_H_
 
 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
+#include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "v8/include/v8.h"
 
@@ -25,7 +26,7 @@
 // with the standard, ReadableStreamDefaultReader is implemented by the
 // ReadableStreamReader class.
 // TODO(ricea): Refactor this when implementing ReadableStreamBYOBReader.
-class ReadableStreamReader : public ScriptWrappable {
+class CORE_EXPORT ReadableStreamReader : public ScriptWrappable {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
diff --git a/third_party/blink/renderer/core/streams/readable_stream_test.cc b/third_party/blink/renderer/core/streams/readable_stream_test.cc
index 656da9d..2217f82 100644
--- a/third_party/blink/renderer/core/streams/readable_stream_test.cc
+++ b/third_party/blink/renderer/core/streams/readable_stream_test.cc
@@ -410,11 +410,6 @@
 }
 
 TEST_P(ReadableStreamTest, Serialize) {
-  if (GetParam()) {
-    // Serialize() is not yet supported in the C++ implementation.
-    return;
-  }
-
   ScopedTransferableStreamsForTest enabled(true);
 
   V8TestingScope scope;
diff --git a/third_party/blink/renderer/core/streams/transferable_streams.cc b/third_party/blink/renderer/core/streams/transferable_streams.cc
new file mode 100644
index 0000000..ee7e1d25
--- /dev/null
+++ b/third_party/blink/renderer/core/streams/transferable_streams.cc
@@ -0,0 +1,875 @@
+// 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.
+
+// Functions for transferable streams. See design doc
+// https://docs.google.com/document/d/1_KuZzg5c3pncLJPFa8SuVm23AP4tft6mzPCL5at3I9M/edit
+
+#include "third_party/blink/renderer/core/streams/transferable_streams.h"
+
+#include "base/stl_util.h"
+#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_dom_exception.h"
+#include "third_party/blink/renderer/core/dom/dom_exception.h"
+#include "third_party/blink/renderer/core/dom/events/native_event_listener.h"
+#include "third_party/blink/renderer/core/events/message_event.h"
+#include "third_party/blink/renderer/core/messaging/message_port.h"
+#include "third_party/blink/renderer/core/messaging/post_message_options.h"
+#include "third_party/blink/renderer/core/streams/miscellaneous_operations.h"
+#include "third_party/blink/renderer/core/streams/promise_handler.h"
+#include "third_party/blink/renderer/core/streams/readable_stream_default_controller.h"
+#include "third_party/blink/renderer/core/streams/readable_stream_native.h"
+#include "third_party/blink/renderer/core/streams/stream_algorithms.h"
+#include "third_party/blink/renderer/core/streams/stream_promise_resolver.h"
+#include "third_party/blink/renderer/core/streams/writable_stream_default_controller.h"
+#include "third_party/blink/renderer/core/streams/writable_stream_native.h"
+#include "third_party/blink/renderer/platform/bindings/exception_state.h"
+#include "third_party/blink/renderer/platform/bindings/script_state.h"
+#include "third_party/blink/renderer/platform/bindings/v8_binding.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
+#include "third_party/blink/renderer/platform/heap/visitor.h"
+#include "third_party/blink/renderer/platform/wtf/assertions.h"
+#include "v8/include/v8.h"
+
+// See the design doc at
+// https://docs.google.com/document/d/1_KuZzg5c3pncLJPFa8SuVm23AP4tft6mzPCL5at3I9M/edit
+// for explanation of how transferable streams are constructed from the "cross
+// realm identity transform" implemented in this file.
+
+// The peer (the other end of the MessagePort) is untrusted as it may be
+// compromised. This means we have to be very careful in unpacking the messages
+// from the peer. LOG(WARNING) is used for cases where a message from the peer
+// appears to be invalid. If this appears during ordinary testing it indicates a
+// bug.
+//
+// The -vmodule=transferable_streams=3 command-line argument can be used for
+// debugging of the protocol.
+
+namespace blink {
+
+namespace {
+
+// These are the types of messages that are sent between peers.
+enum class MessageType { kPull, kCancel, kChunk, kClose, kAbort, kError };
+
+// These are the different ways an error reason can be encoded.
+enum class ErrorType { kTypeError, kJson, kDomException, kUndefined };
+
+bool IsATypeError(ScriptState* script_state, v8::Local<v8::Object> object) {
+  // There isn't a 100% reliable way to identify a TypeError.
+  return object->IsNativeError() &&
+         object->GetConstructorName()
+             ->Equals(script_state->GetContext(),
+                      V8AtomicString(script_state->GetIsolate(), "TypeError"))
+             .ToChecked();
+}
+
+bool IsADOMException(v8::Isolate* isolate, v8::Local<v8::Object> object) {
+  return V8DOMException::HasInstance(object, isolate);
+}
+
+// Creates a JavaScript object with a null prototype structured like {key1:
+// value2, key2: value2}. This is used to create objects to be serialized by
+// postMessage.
+v8::Local<v8::Object> CreateKeyValueObject(v8::Isolate* isolate,
+                                           const char* key1,
+                                           v8::Local<v8::Value> value1,
+                                           const char* key2,
+                                           v8::Local<v8::Value> value2) {
+  v8::Local<v8::Name> names[] = {V8AtomicString(isolate, key1),
+                                 V8AtomicString(isolate, key2)};
+  v8::Local<v8::Value> values[] = {value1, value2};
+  static_assert(base::size(names) == base::size(values),
+                "names and values arrays must be the same size");
+  return v8::Object::New(isolate, v8::Null(isolate), names, values,
+                         base::size(names));
+}
+
+// Unpacks an object created by CreateKeyValueObject(). |value1| and |value2|
+// are out parameters. Returns false on failure.
+bool UnpackKeyValueObject(ScriptState* script_state,
+                          v8::Local<v8::Object> object,
+                          const char* key1,
+                          v8::Local<v8::Value>* value1,
+                          const char* key2,
+                          v8::Local<v8::Value>* value2) {
+  auto* isolate = script_state->GetIsolate();
+  v8::TryCatch try_catch(isolate);
+  auto context = script_state->GetContext();
+  if (!object->Get(context, V8AtomicString(isolate, key1)).ToLocal(value1)) {
+    DLOG(WARNING) << "Error reading key: '" << key1 << "'";
+    return false;
+  }
+  if (!object->Get(context, V8AtomicString(isolate, key2)).ToLocal(value2)) {
+    DLOG(WARNING) << "Error reading key: '" << key2 << "'";
+    return false;
+  }
+  return true;
+}
+
+// Send a message with type |type| and contents |value| over |port|. The type
+// will be packed as a number with key "t", and the value will be packed with
+// key "v".
+void PackAndPostMessage(ScriptState* script_state,
+                        MessagePort* port,
+                        MessageType type,
+                        v8::Local<v8::Value> value,
+                        ExceptionState& exception_state) {
+  DVLOG(3) << "PackAndPostMessage sending message type "
+           << static_cast<int>(type);
+  auto* isolate = script_state->GetIsolate();
+  v8::Local<v8::Object> packed = CreateKeyValueObject(
+      isolate, "t", v8::Number::New(isolate, static_cast<int>(type)), "v",
+      value);
+  port->postMessage(script_state, ScriptValue(script_state, packed),
+                    PostMessageOptions::Create(), exception_state);
+}
+
+// Packs an error into an {e: number, s: string} object for transmission by
+// postMessage. Serializing the resulting object should never fail.
+v8::Local<v8::Object> PackErrorType(v8::Isolate* isolate,
+                                    ErrorType type,
+                                    v8::Local<v8::String> string) {
+  auto error_as_number = v8::Number::New(isolate, static_cast<int>(type));
+  return CreateKeyValueObject(isolate, "e", error_as_number, "s", string);
+}
+
+// Overload for the common case where |string| is a compile-time constant.
+v8::Local<v8::Object> PackErrorType(v8::Isolate* isolate,
+                                    ErrorType type,
+                                    const char* string) {
+  return PackErrorType(isolate, type, V8String(isolate, string));
+}
+
+// We'd like to able to transfer TypeError exceptions, but we can't, so we hack
+// around it. PackReason() is guaranteed to succeed and the object produced is
+// guaranteed to be serializable by postMessage(), however data may be lost. It
+// is not very efficient, and has fairly arbitrary semantics.
+// TODO(ricea): Replace once Errors are serializable.
+v8::Local<v8::Value> PackReason(ScriptState* script_state,
+                                v8::Local<v8::Value> reason) {
+  auto* isolate = script_state->GetIsolate();
+  auto context = script_state->GetContext();
+  if (reason->IsString() || reason->IsNumber() || reason->IsBoolean()) {
+    v8::TryCatch try_catch(isolate);
+    v8::Local<v8::String> stringified;
+    if (!v8::JSON::Stringify(context, reason).ToLocal(&stringified)) {
+      return PackErrorType(isolate, ErrorType::kTypeError,
+                           "Cannot transfer message");
+    }
+
+    return PackErrorType(isolate, ErrorType::kJson, stringified);
+  }
+
+  if (reason->IsFunction() || reason->IsSymbol() ||
+      !(reason->IsObject() || reason->IsNull())) {
+    // Squash to undefined
+    return PackErrorType(isolate, ErrorType::kUndefined, "");
+  }
+
+  if (IsATypeError(script_state, reason.As<v8::Object>())) {
+    v8::TryCatch try_catch(isolate);
+    // "message" on TypeError is a normal property, meaning that if it
+    // is set, it is set on the object itself. We can take advantage of
+    // this to avoid executing user JavaScript in the case when the
+    // TypeError was generated internally.
+    v8::Local<v8::Value> descriptor;
+    if (!reason.As<v8::Object>()
+             ->GetOwnPropertyDescriptor(context,
+                                        V8AtomicString(isolate, "message"))
+             .ToLocal(&descriptor)) {
+      return PackErrorType(isolate, ErrorType::kTypeError,
+                           "Cannot transfer message");
+    }
+    if (descriptor->IsUndefined()) {
+      return PackErrorType(isolate, ErrorType::kTypeError, "");
+    }
+    v8::Local<v8::Value> message;
+    CHECK(descriptor->IsObject());
+    if (!descriptor.As<v8::Object>()
+             ->Get(context, V8AtomicString(isolate, "value"))
+             .ToLocal(&message)) {
+      message = V8String(isolate, "Cannot transfer message");
+    } else if (!message->IsString()) {
+      message = V8String(isolate, "");
+    }
+    return PackErrorType(isolate, ErrorType::kTypeError,
+                         message.As<v8::String>());
+  }
+
+  if (IsADOMException(isolate, reason.As<v8::Object>())) {
+    DOMException* dom_exception =
+        V8DOMException::ToImpl(reason.As<v8::Object>());
+    String message = dom_exception->message();
+    String name = dom_exception->name();
+    v8::Local<v8::Value> packed = CreateKeyValueObject(
+        isolate, "m", V8String(isolate, message), "n", V8String(isolate, name));
+    // It should be impossible for this to fail, except for out-of-memory.
+    v8::Local<v8::String> packed_string =
+        v8::JSON::Stringify(context, packed).ToLocalChecked();
+    return PackErrorType(isolate, ErrorType::kDomException, packed_string);
+  }
+
+  v8::TryCatch try_catch(isolate);
+  v8::Local<v8::Value> json;
+  if (!v8::JSON::Stringify(context, reason).ToLocal(&json)) {
+    return PackErrorType(isolate, ErrorType::kTypeError,
+                         "Cannot transfer message");
+  }
+
+  return PackErrorType(isolate, ErrorType::kJson, json.As<v8::String>());
+}
+
+// Converts an object created by PackReason() back into a clone of the original
+// object, minus any data that was discarded by PackReason().
+bool UnpackReason(ScriptState* script_state,
+                  v8::Local<v8::Value> packed_reason,
+                  v8::Local<v8::Value>* reason) {
+  // We need to be robust against malformed input because it could come from a
+  // compromised renderer.
+  if (!packed_reason->IsObject()) {
+    DLOG(WARNING) << "packed_reason is not an object";
+    return false;
+  }
+
+  v8::Local<v8::Value> encoder_value;
+  v8::Local<v8::Value> string_value;
+  if (!UnpackKeyValueObject(script_state, packed_reason.As<v8::Object>(), "e",
+                            &encoder_value, "s", &string_value)) {
+    return false;
+  }
+
+  if (!encoder_value->IsNumber()) {
+    DLOG(WARNING) << "encoder_value is not a number";
+    return false;
+  }
+
+  int encoder = encoder_value.As<v8::Number>()->Value();
+  if (!string_value->IsString()) {
+    DLOG(WARNING) << "string_value is not a string";
+    return false;
+  }
+
+  v8::Local<v8::String> string = string_value.As<v8::String>();
+  auto* isolate = script_state->GetIsolate();
+  auto context = script_state->GetContext();
+  switch (static_cast<ErrorType>(encoder)) {
+    case ErrorType::kJson: {
+      v8::TryCatch try_catch(isolate);
+      if (!v8::JSON::Parse(context, string).ToLocal(reason)) {
+        DLOG(WARNING) << "JSON Parse failed. Content: " << ToCoreString(string);
+        return false;
+      }
+      return true;
+    }
+
+    case ErrorType::kTypeError:
+      *reason = v8::Exception::TypeError(string);
+      return true;
+
+    case ErrorType::kDomException: {
+      v8::TryCatch try_catch(isolate);
+      v8::Local<v8::Value> packed_exception;
+      if (!v8::JSON::Parse(context, string).ToLocal(&packed_exception)) {
+        DLOG(WARNING) << "Packed DOMException JSON parse failed";
+        return false;
+      }
+
+      if (!packed_exception->IsObject()) {
+        DLOG(WARNING) << "Packed DOMException is not an object";
+        return false;
+      }
+
+      v8::Local<v8::Value> message;
+      v8::Local<v8::Value> name;
+      if (!UnpackKeyValueObject(script_state, packed_exception.As<v8::Object>(),
+                                "m", &message, "n", &name)) {
+        DLOG(WARNING) << "Failed unpacking packed DOMException";
+        return false;
+      }
+
+      if (!message->IsString()) {
+        DLOG(WARNING) << "DOMException message is not a string";
+        return false;
+      }
+
+      if (!name->IsString()) {
+        DLOG(WARNING) << "DOMException name is not a string";
+        return false;
+      }
+
+      auto ToBlink = [](v8::Local<v8::Value> value) {
+        return ToBlinkString<String>(value.As<v8::String>(), kDoNotExternalize);
+      };
+      *reason = ToV8(DOMException::Create(ToBlink(message), ToBlink(name)),
+                     script_state);
+      return true;
+    }
+
+    case ErrorType::kUndefined:
+      *reason = v8::Undefined(isolate);
+      return true;
+
+    default:
+      DLOG(WARNING) << "Invalid ErrorType: " << encoder;
+      return false;
+  }
+}
+
+// Base class for CrossRealmTransformWritable and CrossRealmTransformReadable.
+// Contains common methods that are used when handling MessagePort events.
+class CrossRealmTransformStream
+    : public GarbageCollected<CrossRealmTransformStream> {
+ public:
+  // Neither of the subclasses require finalization, so no destructor.
+
+  virtual ScriptState* GetScriptState() const = 0;
+  virtual MessagePort* GetMessagePort() const = 0;
+
+  // HandleMessage() is called by CrossRealmTransformMessageListener to handle
+  // an incoming message from the MessagePort.
+  virtual void HandleMessage(MessageType type, v8::Local<v8::Value> value) = 0;
+
+  // HandleError() is called by CrossRealmTransformErrorListener when an error
+  // event is fired on the message port. It should error the stream.
+  virtual void HandleError(v8::Local<v8::Value> error) = 0;
+
+  virtual void Trace(Visitor*) {}
+};
+
+// Handles MessageEvents from the MessagePort.
+class CrossRealmTransformMessageListener final : public NativeEventListener {
+ public:
+  explicit CrossRealmTransformMessageListener(CrossRealmTransformStream* target)
+      : target_(target) {}
+
+  void Invoke(ExecutionContext*, Event* event) override {
+    // TODO(ricea): Find a way to guarantee this cast is safe.
+    MessageEvent* message = static_cast<MessageEvent*>(event);
+    ScriptState* script_state = target_->GetScriptState();
+    // The deserializer code called by message->data() looks up the ScriptState
+    // from the current context, so we need to make sure it is set.
+    ScriptState::Scope scope(script_state);
+    v8::Local<v8::Value> data = message->data(script_state).V8Value();
+    if (!data->IsObject()) {
+      DLOG(WARNING) << "Invalid message from peer ignored (not object)";
+      return;
+    }
+
+    v8::Local<v8::Value> type;
+    v8::Local<v8::Value> value;
+    if (!UnpackKeyValueObject(script_state, data.As<v8::Object>(), "t", &type,
+                              "v", &value)) {
+      DLOG(WARNING) << "Invalid message from peer ignored";
+      return;
+    }
+
+    if (!type->IsNumber()) {
+      DLOG(WARNING) << "Invalid message from peer ignored (type is not number)";
+      return;
+    }
+
+    int type_value = type.As<v8::Number>()->Value();
+    DVLOG(3) << "MessageListener saw message type " << type_value;
+    target_->HandleMessage(static_cast<MessageType>(type_value), value);
+  }
+
+  void Trace(Visitor* visitor) override {
+    visitor->Trace(target_);
+    NativeEventListener::Trace(visitor);
+  }
+
+ private:
+  const Member<CrossRealmTransformStream> target_;
+};
+
+// Handles "error" events from the MessagePort.
+class CrossRealmTransformErrorListener final : public NativeEventListener {
+ public:
+  explicit CrossRealmTransformErrorListener(CrossRealmTransformStream* target)
+      : target_(target) {}
+
+  void Invoke(ExecutionContext*, Event*) override {
+    ScriptState* script_state = target_->GetScriptState();
+    const auto* error =
+        DOMException::Create("chunk could not be cloned", "DataCloneError");
+    auto* message_port = target_->GetMessagePort();
+    v8::Local<v8::Value> error_value = ToV8(error, script_state);
+    ExceptionState exception_state(script_state->GetIsolate(),
+                                   ExceptionState::kUnknownContext, "", "");
+
+    PackAndPostMessage(script_state, message_port, MessageType::kError,
+                       PackReason(script_state, error_value), exception_state);
+    if (exception_state.HadException()) {
+      DLOG(WARNING) << "Ignoring postMessage failure in error listener";
+      exception_state.ClearException();
+    }
+
+    message_port->close();
+    target_->HandleError(error_value);
+  }
+
+  void Trace(Visitor* visitor) override {
+    visitor->Trace(target_);
+    NativeEventListener::Trace(visitor);
+  }
+
+ private:
+  const Member<CrossRealmTransformStream> target_;
+};
+
+// Class for data associated with the writable side of the cross realm transform
+// stream.
+class CrossRealmTransformWritable final : public CrossRealmTransformStream {
+ public:
+  CrossRealmTransformWritable(ScriptState* script_state, MessagePort* port)
+      : script_state_(script_state),
+        message_port_(port),
+        backpressure_promise_(
+            MakeGarbageCollected<StreamPromiseResolver>(script_state)) {}
+
+  WritableStreamNative* CreateWritableStream(ExceptionState&);
+
+  ScriptState* GetScriptState() const override { return script_state_; }
+  MessagePort* GetMessagePort() const override { return message_port_; }
+  void HandleMessage(MessageType type, v8::Local<v8::Value> value) override;
+  void HandleError(v8::Local<v8::Value> error) override;
+
+  void Trace(Visitor* visitor) override {
+    visitor->Trace(script_state_);
+    visitor->Trace(message_port_);
+    visitor->Trace(backpressure_promise_);
+    visitor->Trace(controller_);
+    CrossRealmTransformStream::Trace(visitor);
+  }
+
+ private:
+  class WriteAlgorithm;
+  class CloseAlgorithm;
+  class AbortAlgorithm;
+
+  const Member<ScriptState> script_state_;
+  const Member<MessagePort> message_port_;
+  Member<StreamPromiseResolver> backpressure_promise_;
+  Member<WritableStreamDefaultController> controller_;
+};
+
+class CrossRealmTransformWritable::WriteAlgorithm final
+    : public StreamAlgorithm {
+ public:
+  explicit WriteAlgorithm(CrossRealmTransformWritable* writable)
+      : writable_(writable) {}
+
+  // Sends the chunk to the readable side, possibly after waiting for
+  // backpressure.
+  v8::Local<v8::Promise> Run(ScriptState* script_state,
+                             int argc,
+                             v8::Local<v8::Value> argv[]) override {
+    DCHECK_EQ(argc, 1);
+    auto chunk = argv[0];
+
+    if (!writable_->backpressure_promise_) {
+      return DoWrite(script_state, chunk);
+    }
+
+    auto* isolate = script_state->GetIsolate();
+    return StreamThenPromise(
+        script_state->GetContext(),
+        writable_->backpressure_promise_->V8Promise(isolate),
+        MakeGarbageCollected<DoWriteOnResolve>(script_state, chunk, this));
+  }
+
+  void Trace(Visitor* visitor) override {
+    visitor->Trace(writable_);
+    StreamAlgorithm::Trace(visitor);
+  }
+
+ private:
+  // A promise handler which calls DoWrite() when the promise resolves.
+  class DoWriteOnResolve final : public PromiseHandlerWithValue {
+   public:
+    DoWriteOnResolve(ScriptState* script_state,
+                     v8::Local<v8::Value> chunk,
+                     WriteAlgorithm* target)
+        : PromiseHandlerWithValue(script_state),
+          chunk_(script_state->GetIsolate(), chunk),
+          target_(target) {}
+
+    v8::Local<v8::Value> CallWithLocal(v8::Local<v8::Value>) override {
+      ScriptState* script_state = GetScriptState();
+      return target_->DoWrite(script_state,
+                              chunk_.NewLocal(script_state->GetIsolate()));
+    }
+
+    void Trace(Visitor* visitor) override {
+      visitor->Trace(chunk_);
+      visitor->Trace(target_);
+      PromiseHandlerWithValue::Trace(visitor);
+    }
+
+   private:
+    const TraceWrapperV8Reference<v8::Value> chunk_;
+    const Member<WriteAlgorithm> target_;
+  };
+
+  // Sends a chunk over the message port to the readable side.
+  v8::Local<v8::Promise> DoWrite(ScriptState* script_state,
+                                 v8::Local<v8::Value> chunk) {
+    writable_->backpressure_promise_ =
+        MakeGarbageCollected<StreamPromiseResolver>(script_state);
+    ExceptionState exception_state(script_state->GetIsolate(),
+                                   ExceptionState::kUnknownContext, "", "");
+    PackAndPostMessage(script_state, writable_->message_port_,
+                       MessageType::kChunk, chunk, exception_state);
+    if (exception_state.HadException()) {
+      auto exception = exception_state.GetException();
+      exception_state.ClearException();
+
+      PackAndPostMessage(
+          script_state, writable_->message_port_, MessageType::kError,
+          PackReason(writable_->script_state_, exception), exception_state);
+      if (exception_state.HadException()) {
+        DLOG(WARNING) << "Disregarding exception while sending error";
+        exception_state.ClearException();
+      }
+
+      writable_->message_port_->close();
+      return PromiseReject(script_state, exception);
+    }
+
+    return PromiseResolveWithUndefined(script_state);
+  }
+
+  const Member<CrossRealmTransformWritable> writable_;
+};
+
+class CrossRealmTransformWritable::CloseAlgorithm final
+    : public StreamAlgorithm {
+ public:
+  explicit CloseAlgorithm(CrossRealmTransformWritable* writable)
+      : writable_(writable) {}
+
+  // Sends a close message to the readable side and closes the message port.
+  v8::Local<v8::Promise> Run(ScriptState* script_state,
+                             int argc,
+                             v8::Local<v8::Value> argv[]) override {
+    DCHECK_EQ(argc, 0);
+    ExceptionState exception_state(script_state->GetIsolate(),
+                                   ExceptionState::kUnknownContext, "", "");
+    PackAndPostMessage(
+        script_state, writable_->message_port_, MessageType::kClose,
+        v8::Undefined(script_state->GetIsolate()), exception_state);
+    if (exception_state.HadException()) {
+      DLOG(WARNING) << "Ignoring exception from PackAndPostMessage kClose";
+      exception_state.ClearException();
+    }
+
+    writable_->message_port_->close();
+    return PromiseResolveWithUndefined(script_state);
+  }
+
+  void Trace(Visitor* visitor) override {
+    visitor->Trace(writable_);
+    StreamAlgorithm::Trace(visitor);
+  }
+
+ private:
+  const Member<CrossRealmTransformWritable> writable_;
+};
+
+class CrossRealmTransformWritable::AbortAlgorithm final
+    : public StreamAlgorithm {
+ public:
+  explicit AbortAlgorithm(CrossRealmTransformWritable* writable)
+      : writable_(writable) {}
+
+  // Sends an abort message to the readable side and closes the message port.
+  v8::Local<v8::Promise> Run(ScriptState* script_state,
+                             int argc,
+                             v8::Local<v8::Value> argv[]) override {
+    DCHECK_EQ(argc, 1);
+    auto reason = argv[0];
+    ExceptionState exception_state(script_state->GetIsolate(),
+                                   ExceptionState::kUnknownContext, "", "");
+    PackAndPostMessage(
+        script_state, writable_->message_port_, MessageType::kAbort,
+        PackReason(writable_->script_state_, reason), exception_state);
+    if (exception_state.HadException()) {
+      DLOG(WARNING) << "Ignoring exception from PackAndPostMessage kAbort";
+      exception_state.ClearException();
+    }
+    writable_->message_port_->close();
+    return PromiseResolveWithUndefined(script_state);
+  }
+
+  void Trace(Visitor* visitor) override {
+    visitor->Trace(writable_);
+    StreamAlgorithm::Trace(visitor);
+  }
+
+ private:
+  const Member<CrossRealmTransformWritable> writable_;
+};
+
+WritableStreamNative* CrossRealmTransformWritable::CreateWritableStream(
+    ExceptionState& exception_state) {
+  DCHECK(!controller_) << "CreateWritableStream() can only be called once";
+
+  message_port_->setOnmessage(
+      MakeGarbageCollected<CrossRealmTransformMessageListener>(this));
+  message_port_->setOnmessageerror(
+      MakeGarbageCollected<CrossRealmTransformErrorListener>(this));
+
+  auto* stream = WritableStreamNative::Create(
+      script_state_, CreateTrivialStartAlgorithm(),
+      MakeGarbageCollected<WriteAlgorithm>(this),
+      MakeGarbageCollected<CloseAlgorithm>(this),
+      MakeGarbageCollected<AbortAlgorithm>(this), 1,
+      CreateDefaultSizeAlgorithm(), exception_state);
+
+  if (exception_state.HadException()) {
+    return nullptr;
+  }
+
+  controller_ = stream->Controller();
+  return stream;
+}
+
+void CrossRealmTransformWritable::HandleMessage(MessageType type,
+                                                v8::Local<v8::Value> value) {
+  switch (type) {
+    case MessageType::kPull:
+      DCHECK(backpressure_promise_);
+      backpressure_promise_->ResolveWithUndefined(script_state_);
+      backpressure_promise_ = nullptr;
+      return;
+
+    case MessageType::kCancel:
+    case MessageType::kError: {
+      v8::Local<v8::Value> reason;
+      if (!UnpackReason(script_state_, value, &reason)) {
+        DLOG(WARNING)
+            << "Invalid message from peer ignored (unable to unpack value)";
+        return;
+      }
+      WritableStreamDefaultController::ErrorIfNeeded(script_state_, controller_,
+                                                     reason);
+      if (backpressure_promise_) {
+        backpressure_promise_->ResolveWithUndefined(script_state_);
+        backpressure_promise_ = nullptr;
+      }
+      return;
+    }
+
+    default:
+      DLOG(WARNING) << "Invalid message from peer ignored (invalid type): "
+                    << static_cast<int>(type);
+      return;
+  }
+}
+
+void CrossRealmTransformWritable::HandleError(v8::Local<v8::Value> error) {
+  WritableStreamDefaultController::ErrorIfNeeded(script_state_, controller_,
+                                                 error);
+}
+
+// Class for data associated with the readable side of the cross realm transform
+// stream.
+class CrossRealmTransformReadable final : public CrossRealmTransformStream {
+ public:
+  CrossRealmTransformReadable(ScriptState* script_state, MessagePort* port)
+      : script_state_(script_state),
+        message_port_(port),
+        backpressure_promise_(
+            MakeGarbageCollected<StreamPromiseResolver>(script_state)) {}
+
+  ReadableStreamNative* CreateReadableStream(ExceptionState&);
+
+  ScriptState* GetScriptState() const override { return script_state_; }
+  MessagePort* GetMessagePort() const override { return message_port_; }
+  void HandleMessage(MessageType type, v8::Local<v8::Value> value) override;
+  void HandleError(v8::Local<v8::Value> error) override;
+
+  void Trace(Visitor* visitor) override {
+    visitor->Trace(script_state_);
+    visitor->Trace(message_port_);
+    visitor->Trace(backpressure_promise_);
+    visitor->Trace(controller_);
+    CrossRealmTransformStream::Trace(visitor);
+  }
+
+ private:
+  class PullAlgorithm;
+  class CancelAlgorithm;
+
+  const Member<ScriptState> script_state_;
+  const Member<MessagePort> message_port_;
+  Member<StreamPromiseResolver> backpressure_promise_;
+  Member<ReadableStreamDefaultController> controller_;
+  bool finished_ = false;
+};
+
+class CrossRealmTransformReadable::PullAlgorithm final
+    : public StreamAlgorithm {
+ public:
+  explicit PullAlgorithm(CrossRealmTransformReadable* readable)
+      : readable_(readable) {}
+
+  // Sends a pull message to the writable side and then waits for backpressure
+  // to clear.
+  v8::Local<v8::Promise> Run(ScriptState* script_state,
+                             int argc,
+                             v8::Local<v8::Value> argv[]) override {
+    DCHECK_EQ(argc, 0);
+    auto* isolate = script_state->GetIsolate();
+    ExceptionState exception_state(isolate, ExceptionState::kUnknownContext, "",
+                                   "");
+
+    PackAndPostMessage(
+        script_state, readable_->message_port_, MessageType::kPull,
+        v8::Undefined(script_state->GetIsolate()), exception_state);
+    if (exception_state.HadException()) {
+      DLOG(WARNING) << "Ignoring exception from PackAndPostMessage kClose";
+      exception_state.ClearException();
+    }
+
+    return readable_->backpressure_promise_->V8Promise(isolate);
+  }
+
+  void Trace(Visitor* visitor) override {
+    visitor->Trace(readable_);
+    StreamAlgorithm::Trace(visitor);
+  }
+
+ private:
+  const Member<CrossRealmTransformReadable> readable_;
+};
+
+class CrossRealmTransformReadable::CancelAlgorithm final
+    : public StreamAlgorithm {
+ public:
+  explicit CancelAlgorithm(CrossRealmTransformReadable* readable)
+      : readable_(readable) {}
+
+  // Sends a cancel message to the writable side and closes the message port.
+  v8::Local<v8::Promise> Run(ScriptState* script_state,
+                             int argc,
+                             v8::Local<v8::Value> argv[]) override {
+    DCHECK_EQ(argc, 1);
+    auto reason = argv[0];
+    readable_->finished_ = true;
+    ExceptionState exception_state(script_state->GetIsolate(),
+                                   ExceptionState::kUnknownContext, "", "");
+
+    PackAndPostMessage(script_state, readable_->message_port_,
+                       MessageType::kCancel, PackReason(script_state, reason),
+                       exception_state);
+    if (exception_state.HadException()) {
+      DLOG(WARNING) << "Ignoring exception from PackAndPostMessage kClose";
+      exception_state.ClearException();
+    }
+
+    readable_->message_port_->close();
+    return PromiseResolveWithUndefined(script_state);
+  }
+
+  void Trace(Visitor* visitor) override {
+    visitor->Trace(readable_);
+    StreamAlgorithm::Trace(visitor);
+  }
+
+ private:
+  const Member<CrossRealmTransformReadable> readable_;
+};
+
+ReadableStreamNative* CrossRealmTransformReadable::CreateReadableStream(
+    ExceptionState& exception_state) {
+  DCHECK(!controller_) << "CreateReadableStream can only be called once";
+
+  message_port_->setOnmessage(
+      MakeGarbageCollected<CrossRealmTransformMessageListener>(this));
+  message_port_->setOnmessageerror(
+      MakeGarbageCollected<CrossRealmTransformErrorListener>(this));
+
+  auto* stream = ReadableStreamNative::Create(
+      script_state_, CreateTrivialStartAlgorithm(),
+      MakeGarbageCollected<PullAlgorithm>(this),
+      MakeGarbageCollected<CancelAlgorithm>(this),
+      /* highWaterMark = */ 0, CreateDefaultSizeAlgorithm(), exception_state);
+
+  if (exception_state.HadException()) {
+    return nullptr;
+  }
+
+  controller_ = stream->GetController();
+  return stream;
+}
+
+void CrossRealmTransformReadable::HandleMessage(MessageType type,
+                                                v8::Local<v8::Value> value) {
+  switch (type) {
+    case MessageType::kChunk: {
+      // This can't throw because we always use the default strategy size
+      // algorithm, which doesn't throw, and always returns a valid value of
+      // 1.0.
+      ReadableStreamDefaultController::Enqueue(script_state_, controller_,
+                                               value, ASSERT_NO_EXCEPTION);
+
+      backpressure_promise_->ResolveWithUndefined(script_state_);
+      backpressure_promise_ =
+          MakeGarbageCollected<StreamPromiseResolver>(script_state_);
+      return;
+    }
+
+    case MessageType::kClose:
+      finished_ = true;
+      ReadableStreamDefaultController::Close(script_state_, controller_);
+      message_port_->close();
+      return;
+
+    case MessageType::kAbort:
+    case MessageType::kError: {
+      finished_ = true;
+      v8::Local<v8::Value> reason;
+      if (!UnpackReason(script_state_, value, &reason)) {
+        DLOG(WARNING)
+            << "Invalid message from peer ignored (unable to unpack value)";
+        return;
+      }
+
+      ReadableStreamDefaultController::Error(script_state_, controller_,
+                                             reason);
+      message_port_->close();
+      return;
+    }
+
+    default:
+      DLOG(WARNING) << "Invalid message from peer ignored (invalid type): "
+                    << static_cast<int>(type);
+      return;
+  }
+}
+
+void CrossRealmTransformReadable::HandleError(v8::Local<v8::Value> error) {
+  ReadableStreamDefaultController::Error(script_state_, controller_, error);
+}
+
+}  // namespace
+
+CORE_EXPORT WritableStreamNative* CreateCrossRealmTransformWritable(
+    ScriptState* script_state,
+    MessagePort* port,
+    ExceptionState& exception_state) {
+  return MakeGarbageCollected<CrossRealmTransformWritable>(script_state, port)
+      ->CreateWritableStream(exception_state);
+}
+
+CORE_EXPORT ReadableStreamNative* CreateCrossRealmTransformReadable(
+    ScriptState* script_state,
+    MessagePort* port,
+    ExceptionState& exception_state) {
+  return MakeGarbageCollected<CrossRealmTransformReadable>(script_state, port)
+      ->CreateReadableStream(exception_state);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/streams/transferable_streams.h b/third_party/blink/renderer/core/streams/transferable_streams.h
new file mode 100644
index 0000000..62883f1
--- /dev/null
+++ b/third_party/blink/renderer/core/streams/transferable_streams.h
@@ -0,0 +1,37 @@
+// 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.
+
+// Functions used to build transferable streams.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_STREAMS_TRANSFERABLE_STREAMS_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_STREAMS_TRANSFERABLE_STREAMS_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+
+namespace blink {
+
+class ExceptionState;
+class MessagePort;
+class ReadableStreamNative;
+class ScriptState;
+class WritableStreamNative;
+
+// Creates the writable side of a cross-realm identity transform stream, using
+// |port| for communication. |port| must be entangled with another MessagePort
+// which is passed to CreateCrossRealmTransformReadable().
+CORE_EXPORT WritableStreamNative* CreateCrossRealmTransformWritable(
+    ScriptState*,
+    MessagePort* port,
+    ExceptionState&);
+
+// Creates the readable side of a cross-realm identity transform stream. |port|
+// is used symmetrically with CreateCrossRealmTransformWritable().
+CORE_EXPORT ReadableStreamNative* CreateCrossRealmTransformReadable(
+    ScriptState*,
+    MessagePort* port,
+    ExceptionState&);
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_STREAMS_TRANSFERABLE_STREAMS_H_
diff --git a/third_party/blink/renderer/core/streams/transferable_streams_test.cc b/third_party/blink/renderer/core/streams/transferable_streams_test.cc
new file mode 100644
index 0000000..3811b05
--- /dev/null
+++ b/third_party/blink/renderer/core/streams/transferable_streams_test.cc
@@ -0,0 +1,129 @@
+// 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/core/streams/transferable_streams.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/bindings/core/v8/script_function.h"
+#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
+#include "third_party/blink/renderer/bindings/core/v8/script_value.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_iterator_result_value.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_readable_stream_default_reader.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_writable_stream_default_writer.h"
+#include "third_party/blink/renderer/core/messaging/message_channel.h"
+#include "third_party/blink/renderer/core/streams/readable_stream_default_reader.h"
+#include "third_party/blink/renderer/core/streams/readable_stream_native.h"
+#include "third_party/blink/renderer/core/streams/writable_stream_default_writer.h"
+#include "third_party/blink/renderer/core/streams/writable_stream_native.h"
+#include "third_party/blink/renderer/platform/bindings/exception_state.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
+#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
+
+namespace blink {
+
+namespace {
+
+// We only do minimal testing here. The functionality of transferable streams is
+// tested in the layout tests.
+TEST(TransferableStreamsTest, SmokeTest) {
+  V8TestingScope scope;
+
+  auto* channel =
+      MakeGarbageCollected<MessageChannel>(scope.GetExecutionContext());
+  auto* script_state = scope.GetScriptState();
+  auto* writable = CreateCrossRealmTransformWritable(
+      script_state, channel->port1(), ASSERT_NO_EXCEPTION);
+  ASSERT_TRUE(writable);
+  auto* readable = CreateCrossRealmTransformReadable(
+      script_state, channel->port2(), ASSERT_NO_EXCEPTION);
+  ASSERT_TRUE(readable);
+
+  auto* writer = V8WritableStreamDefaultWriter::ToImpl(
+      writable->getWriter(script_state, ASSERT_NO_EXCEPTION)
+          .V8Value()
+          .As<v8::Object>());
+  auto* reader = V8ReadableStreamDefaultReader::ToImpl(
+      readable->getReader(script_state, ASSERT_NO_EXCEPTION)
+          .V8Value()
+          .As<v8::Object>());
+
+  writer->write(script_state, ScriptValue::CreateNull(script_state));
+
+  class ExpectNullResponse : public ScriptFunction {
+   public:
+    static v8::Local<v8::Function> Create(ScriptState* script_state,
+                                          bool* got_response) {
+      auto* self =
+          MakeGarbageCollected<ExpectNullResponse>(script_state, got_response);
+      return self->BindToV8Function();
+    }
+
+    ExpectNullResponse(ScriptState* script_state, bool* got_response)
+        : ScriptFunction(script_state), got_response_(got_response) {}
+
+   private:
+    ScriptValue Call(ScriptValue value) override {
+      *got_response_ = true;
+      if (!value.IsObject()) {
+        ADD_FAILURE() << "iterator must be an object";
+        return ScriptValue();
+      }
+      bool done = false;
+      auto* script_state = GetScriptState();
+      auto chunk_maybe =
+          V8UnpackIteratorResult(script_state,
+                                 value.V8Value()
+                                     ->ToObject(script_state->GetContext())
+                                     .ToLocalChecked(),
+                                 &done);
+      EXPECT_FALSE(done);
+      v8::Local<v8::Value> chunk;
+      if (!chunk_maybe.ToLocal(&chunk)) {
+        ADD_FAILURE() << "V8UnpackIteratorResult failed";
+        return ScriptValue();
+      }
+      EXPECT_TRUE(chunk->IsNull());
+      return ScriptValue();
+    }
+
+    bool* got_response_;
+  };
+
+  // TODO(ricea): This is copy-and-pasted from transform_stream_test.cc. Put it
+  // in a shared location.
+  class ExpectNotReached : public ScriptFunction {
+   public:
+    static v8::Local<v8::Function> Create(ScriptState* script_state) {
+      auto* self = MakeGarbageCollected<ExpectNotReached>(script_state);
+      return self->BindToV8Function();
+    }
+
+    explicit ExpectNotReached(ScriptState* script_state)
+        : ScriptFunction(script_state) {}
+
+   private:
+    ScriptValue Call(ScriptValue) override {
+      ADD_FAILURE() << "ExpectNotReached was reached";
+      return ScriptValue();
+    }
+  };
+
+  bool got_response = false;
+  reader->read(script_state)
+      .Then(ExpectNullResponse::Create(script_state, &got_response),
+            ExpectNotReached::Create(script_state));
+
+  // Need to run the event loop to pass messages through the MessagePort.
+  test::RunPendingTasks();
+
+  // Resolve promises.
+  v8::MicrotasksScope::PerformCheckpoint(scope.GetIsolate());
+
+  EXPECT_TRUE(got_response);
+}
+
+}  // namespace
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/streams/writable_stream.cc b/third_party/blink/renderer/core/streams/writable_stream.cc
index da3db6e..3d6f944 100644
--- a/third_party/blink/renderer/core/streams/writable_stream.cc
+++ b/third_party/blink/renderer/core/streams/writable_stream.cc
@@ -57,7 +57,10 @@
 WritableStream* WritableStream::Deserialize(ScriptState* script_state,
                                             MessagePort* port,
                                             ExceptionState& exception_state) {
-  // TODO(ricea): Switch on Blink feature.
+  if (RuntimeEnabledFeatures::StreamsNativeEnabled()) {
+    return WritableStreamNative::Deserialize(script_state, port,
+                                             exception_state);
+  }
   return WritableStreamWrapper::Deserialize(script_state, port,
                                             exception_state);
 }
diff --git a/third_party/blink/renderer/core/streams/writable_stream_default_writer.h b/third_party/blink/renderer/core/streams/writable_stream_default_writer.h
index 1bb5bea..fbe239e 100644
--- a/third_party/blink/renderer/core/streams/writable_stream_default_writer.h
+++ b/third_party/blink/renderer/core/streams/writable_stream_default_writer.h
@@ -6,6 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_STREAMS_WRITABLE_STREAM_DEFAULT_WRITER_H_
 
 #include "base/optional.h"
+#include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "v8/include/v8.h"
 
@@ -21,7 +22,7 @@
 class WritableStreamNative;
 
 // https://streams.spec.whatwg.org/#default-writer-class
-class WritableStreamDefaultWriter final : public ScriptWrappable {
+class CORE_EXPORT WritableStreamDefaultWriter final : public ScriptWrappable {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
diff --git a/third_party/blink/renderer/core/streams/writable_stream_native.cc b/third_party/blink/renderer/core/streams/writable_stream_native.cc
index 262a9833..c0ee787 100644
--- a/third_party/blink/renderer/core/streams/writable_stream_native.cc
+++ b/third_party/blink/renderer/core/streams/writable_stream_native.cc
@@ -7,7 +7,9 @@
 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
 #include "third_party/blink/renderer/core/streams/miscellaneous_operations.h"
 #include "third_party/blink/renderer/core/streams/promise_handler.h"
+#include "third_party/blink/renderer/core/streams/readable_stream_native.h"
 #include "third_party/blink/renderer/core/streams/stream_promise_resolver.h"
+#include "third_party/blink/renderer/core/streams/transferable_streams.h"
 #include "third_party/blink/renderer/core/streams/writable_stream_default_controller.h"
 #include "third_party/blink/renderer/core/streams/writable_stream_default_writer.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
@@ -215,6 +217,41 @@
   return stream;
 }
 
+void WritableStreamNative::Serialize(ScriptState* script_state,
+                                     MessagePort* port,
+                                     ExceptionState& exception_state) {
+  if (IsLocked(this)) {
+    exception_state.ThrowTypeError("Cannot transfer a locked stream");
+    return;
+  }
+
+  auto* readable =
+      CreateCrossRealmTransformReadable(script_state, port, exception_state);
+  if (exception_state.HadException()) {
+    return;
+  }
+
+  auto promise = ReadableStreamNative::PipeTo(
+      script_state, readable, this, ReadableStreamNative::PipeOptions());
+  promise.MarkAsHandled();
+}
+
+WritableStreamNative* WritableStreamNative::Deserialize(
+    ScriptState* script_state,
+    MessagePort* port,
+    ExceptionState& exception_state) {
+  // We need to execute JavaScript to call "Then" on v8::Promises. We will not
+  // run author code.
+  v8::Isolate::AllowJavascriptExecutionScope allow_js(
+      script_state->GetIsolate());
+  auto* writable =
+      CreateCrossRealmTransformWritable(script_state, port, exception_state);
+  if (exception_state.HadException()) {
+    return nullptr;
+  }
+  return writable;
+}
+
 WritableStreamDefaultWriter* WritableStreamNative::AcquireDefaultWriter(
     ScriptState* script_state,
     WritableStreamNative* stream,
diff --git a/third_party/blink/renderer/core/streams/writable_stream_native.h b/third_party/blink/renderer/core/streams/writable_stream_native.h
index 64f80fc2..e1848c6 100644
--- a/third_party/blink/renderer/core/streams/writable_stream_native.h
+++ b/third_party/blink/renderer/core/streams/writable_stream_native.h
@@ -83,9 +83,11 @@
     return stream->writer_;
   }
 
-  void Serialize(ScriptState*, MessagePort*, ExceptionState&) override {
-    // TODO(ricea): Implement this.
-  }
+  void Serialize(ScriptState*, MessagePort*, ExceptionState&) override;
+
+  static WritableStreamNative* Deserialize(ScriptState*,
+                                           MessagePort*,
+                                           ExceptionState&);
 
   //
   // Methods used by ReadableStreamNative::PipeTo
diff --git a/third_party/blink/renderer/core/streams/writable_stream_test.cc b/third_party/blink/renderer/core/streams/writable_stream_test.cc
index 5401cc7..e87946d1 100644
--- a/third_party/blink/renderer/core/streams/writable_stream_test.cc
+++ b/third_party/blink/renderer/core/streams/writable_stream_test.cc
@@ -60,12 +60,6 @@
 }
 
 TEST_P(WritableStreamTest, Serialize) {
-  // Disable the test when StreamsNative is enabled as WritableStreamNative
-  // doesn't support serialization yet.
-  // TODO(ricea): Re-enable this test when serialization is supported.
-  if (GetParam())
-    return;
-
   ScopedTransferableStreamsForTest enable_transferable_streams(true);
 
   V8TestingScope scope;
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 0850c24..a29cb66 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -482,6 +482,7 @@
     "exported/mediastream/media_stream_audio_track.cc",
     "exported/mediastream/web_platform_media_stream_source.cc",
     "exported/mediastream/web_platform_media_stream_track.cc",
+    "exported/mediastream/webaudio_media_stream_source.cc",
     "exported/mediastream/webrtc_uma_histograms.cc",
     "exported/platform.cc",
     "exported/service_registry.cc",
diff --git a/third_party/blink/renderer/platform/bindings/script_wrappable.h b/third_party/blink/renderer/platform/bindings/script_wrappable.h
index 45a60f97..2a9c4cc 100644
--- a/third_party/blink/renderer/platform/bindings/script_wrappable.h
+++ b/third_party/blink/renderer/platform/bindings/script_wrappable.h
@@ -55,10 +55,21 @@
  public:
   virtual ~ScriptWrappable() = default;
 
-  virtual void Trace(blink::Visitor*);
+  // The following methods may override lifetime of ScriptWrappable objects when
+  // needed. In particular if |HasPendingActivity| or |HasEventListeners|
+  // returns true *and* the child type also inherits from
+  // |ActiveScriptWrappable|, the objects will not be reclaimed by the GC, even
+  // if they are otherwise unreachable.
+  //
+  // Note: These methods are queried during garbage collection and *must not*
+  // allocate any new objects.
+  virtual bool HasPendingActivity() const { return false; }
+  virtual bool HasEventListeners() const { return false; }
 
   const char* NameInHeapSnapshot() const override;
 
+  virtual void Trace(blink::Visitor*);
+
   template <typename T>
   T* ToImpl() {
     // All ScriptWrappables are managed by the Blink GC heap; check that
@@ -98,11 +109,6 @@
       const WrapperTypeInfo*,
       v8::Local<v8::Object> wrapper);
 
-  // Returns true if the instance needs to be kept alive even when the
-  // instance is unreachable from JavaScript.
-  virtual bool HasPendingActivity() const { return false; }
-  virtual bool HasEventListeners() const { return false; }
-
   // Associates this instance with the given |wrapper| if this instance is not
   // yet associated with any wrapper.  Returns true if the given wrapper is
   // associated with this instance, or false if this instance is already
diff --git a/content/renderer/media/stream/webaudio_media_stream_source.cc b/third_party/blink/renderer/platform/exported/mediastream/webaudio_media_stream_source.cc
similarity index 87%
rename from content/renderer/media/stream/webaudio_media_stream_source.cc
rename to third_party/blink/renderer/platform/exported/mediastream/webaudio_media_stream_source.cc
index 1f1b6dd..8d4398d 100644
--- a/content/renderer/media/stream/webaudio_media_stream_source.cc
+++ b/third_party/blink/renderer/platform/exported/mediastream/webaudio_media_stream_source.cc
@@ -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 "content/renderer/media/stream/webaudio_media_stream_source.h"
+#include "third_party/blink/public/platform/modules/mediastream/webaudio_media_stream_source.h"
 
 #include <utility>
 
@@ -10,13 +10,12 @@
 #include "base/bind_helpers.h"
 #include "base/logging.h"
 
-namespace content {
+namespace blink {
 
 WebAudioMediaStreamSource::WebAudioMediaStreamSource(
-    blink::WebMediaStreamSource* blink_source,
+    WebMediaStreamSource* blink_source,
     scoped_refptr<base::SingleThreadTaskRunner> task_runner)
-    : blink::MediaStreamAudioSource(std::move(task_runner),
-                                    false /* is_remote */),
+    : MediaStreamAudioSource(std::move(task_runner), false /* is_remote */),
       is_registered_consumer_(false),
       fifo_(base::Bind(&WebAudioMediaStreamSource::DeliverRebufferedAudio,
                        base::Unretained(this))),
@@ -53,7 +52,7 @@
                                 fifo_.frames_per_buffer());
   // Take care of the discrete channel layout case.
   params.set_channels_for_discrete(number_of_channels);
-  blink::MediaStreamAudioSource::SetFormat(params);
+  MediaStreamAudioSource::SetFormat(params);
 
   if (!wrapper_bus_ || wrapper_bus_->channels() != params.channels())
     wrapper_bus_ = media::AudioBus::CreateWrapper(params.channels());
@@ -84,7 +83,7 @@
 }
 
 void WebAudioMediaStreamSource::ConsumeAudio(
-    const blink::WebVector<const float*>& audio_data,
+    const WebVector<const float*>& audio_data,
     size_t number_of_frames) {
   // TODO(miu): Plumbing is needed to determine the actual capture timestamp
   // of the audio, instead of just snapshotting TimeTicks::Now(), for proper
@@ -108,8 +107,8 @@
       current_reference_time_ +
       base::TimeDelta::FromMicroseconds(
           frame_delay * base::Time::kMicrosecondsPerSecond /
-          blink::MediaStreamAudioSource::GetAudioParameters().sample_rate());
-  blink::MediaStreamAudioSource::DeliverDataToTracks(audio_bus, reference_time);
+          MediaStreamAudioSource::GetAudioParameters().sample_rate());
+  MediaStreamAudioSource::DeliverDataToTracks(audio_bus, reference_time);
 }
 
-}  // namespace content
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/fonts/font_cache.cc b/third_party/blink/renderer/platform/fonts/font_cache.cc
index 62e6790..32f740f 100644
--- a/third_party/blink/renderer/platform/fonts/font_cache.cc
+++ b/third_party/blink/renderer/platform/fonts/font_cache.cc
@@ -110,6 +110,8 @@
     const FontDescription& font_description,
     const FontFaceCreationParams& creation_params,
     AlternateFontName alternate_font_name) {
+  TRACE_EVENT0("fonts", "FontCache::GetFontPlatformData");
+
   if (!platform_init_) {
     platform_init_ = true;
     PlatformInit();
@@ -193,7 +195,7 @@
     const FontDescription& font_description,
     const FontFaceCreationParams& creation_params,
     float font_size) {
-  TRACE_EVENT0("ui", "FontCache::ScaleFontPlatformData");
+  TRACE_EVENT0("fonts,ui", "FontCache::ScaleFontPlatformData");
 
 #if defined(OS_MACOSX)
   return CreateFontPlatformData(font_description, creation_params, font_size);
@@ -295,6 +297,8 @@
     UChar32 lookup_char,
     const SimpleFontData* font_data_to_substitute,
     FontFallbackPriority fallback_priority) {
+  TRACE_EVENT0("fonts", "FontCache::FallbackFontForCharacter");
+
   // In addition to PUA, do not perform fallback for non-characters either. Some
   // of these are sentinel characters to detect encodings and do appear on
   // websites. More details on
@@ -313,7 +317,7 @@
 }
 
 void FontCache::PurgePlatformFontDataCache() {
-  TRACE_EVENT0("ui", "FontCache::PurgePlatformFontDataCache");
+  TRACE_EVENT0("fonts,ui", "FontCache::PurgePlatformFontDataCache");
   Vector<FontCacheKey> keys_to_remove;
   keys_to_remove.ReserveInitialCapacity(font_platform_data_cache_.size());
   for (auto& sized_fonts : font_platform_data_cache_) {
@@ -332,7 +336,7 @@
 }
 
 void FontCache::PurgeFallbackListShaperCache() {
-  TRACE_EVENT0("ui", "FontCache::PurgeFallbackListShaperCache");
+  TRACE_EVENT0("fonts,ui", "FontCache::PurgeFallbackListShaperCache");
   unsigned items = 0;
   FallbackListShaperCache::iterator iter;
   for (iter = fallback_list_shaper_cache_.begin();
@@ -379,7 +383,7 @@
 }
 
 void FontCache::Invalidate() {
-  TRACE_EVENT0("ui", "FontCache::Invalidate");
+  TRACE_EVENT0("fonts,ui", "FontCache::Invalidate");
   font_platform_data_cache_.clear();
   generation_++;
 
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource.cc b/third_party/blink/renderer/platform/graphics/canvas_resource.cc
index 2eb4228..e459e7a 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource.cc
@@ -579,7 +579,7 @@
       Provider() ? Provider()->ResourceDispatcher() : nullptr;
   if (resource_dispatcher) {
     resource_dispatcher->DidAllocateSharedBitmap(
-        viz::bitmap_allocation::ToMojoHandle(std::move(shm.region)),
+        std::move(shm.region),
         SharedBitmapIdToGpuMailboxPtr(shared_bitmap_id_));
   }
 }
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc b/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc
index feb9021..33357b7e 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc
@@ -388,10 +388,10 @@
 }
 
 void CanvasResourceDispatcher::DidAllocateSharedBitmap(
-    mojo::ScopedSharedBufferHandle buffer,
+    base::ReadOnlySharedMemoryRegion region,
     ::gpu::mojom::blink::MailboxPtr id) {
   if (sink_)
-    sink_->DidAllocateSharedBitmap(std::move(buffer), std::move(id));
+    sink_->DidAllocateSharedBitmap(std::move(region), std::move(id));
 }
 
 void CanvasResourceDispatcher::DidDeleteSharedBitmap(
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.h b/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.h
index 8d95154..323e932d 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.h
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.h
@@ -6,6 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_CANVAS_RESOURCE_DISPATCHER_H_
 
 #include <memory>
+#include "base/memory/read_only_shared_memory_region.h"
 #include "components/viz/common/frame_sinks/begin_frame_args.h"
 #include "components/viz/common/resources/resource_id.h"
 #include "components/viz/common/surfaces/parent_local_surface_id_allocator.h"
@@ -71,7 +72,7 @@
   void ReclaimResources(
       const WTF::Vector<viz::ReturnedResource>& resources) final;
 
-  void DidAllocateSharedBitmap(mojo::ScopedSharedBufferHandle buffer,
+  void DidAllocateSharedBitmap(base::ReadOnlySharedMemoryRegion region,
                                ::gpu::mojom::blink::MailboxPtr id);
   void DidDeleteSharedBitmap(::gpu::mojom::blink::MailboxPtr id);
 
diff --git a/third_party/blink/renderer/platform/graphics/test/mock_compositor_frame_sink.h b/third_party/blink/renderer/platform/graphics/test/mock_compositor_frame_sink.h
index 499d543..c764ddf 100644
--- a/third_party/blink/renderer/platform/graphics/test/mock_compositor_frame_sink.h
+++ b/third_party/blink/renderer/platform/graphics/test/mock_compositor_frame_sink.h
@@ -5,6 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_TEST_MOCK_COMPOSITOR_FRAME_SINK_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_TEST_MOCK_COMPOSITOR_FRAME_SINK_H_
 
+#include "base/memory/read_only_shared_memory_region.h"
 #include "components/viz/common/quads/compositor_frame.h"
 #include "gpu/ipc/common/mailbox.mojom-blink.h"
 #include "services/viz/public/interfaces/compositing/compositor_frame_sink.mojom-blink.h"
@@ -49,7 +50,7 @@
   MOCK_METHOD1(SubmitCompositorFrameSync_, void(viz::CompositorFrame*));
   MOCK_METHOD1(DidNotProduceFrame, void(const viz::BeginFrameAck&));
   MOCK_METHOD2(DidAllocateSharedBitmap,
-               void(mojo::ScopedSharedBufferHandle,
+               void(base::ReadOnlySharedMemoryRegion,
                     gpu::mojom::blink::MailboxPtr));
   MOCK_METHOD1(DidDeleteSharedBitmap, void(gpu::mojom::blink::MailboxPtr));
   MOCK_METHOD1(SetPreferredFrameInterval, void(base::TimeDelta));
diff --git a/third_party/blink/renderer/platform/graphics/video_frame_submitter.cc b/third_party/blink/renderer/platform/graphics/video_frame_submitter.cc
index e7b9b44..cf35222 100644
--- a/third_party/blink/renderer/platform/graphics/video_frame_submitter.cc
+++ b/third_party/blink/renderer/platform/graphics/video_frame_submitter.cc
@@ -234,12 +234,12 @@
 }
 
 void VideoFrameSubmitter::DidAllocateSharedBitmap(
-    mojo::ScopedSharedBufferHandle buffer,
+    base::ReadOnlySharedMemoryRegion region,
     const viz::SharedBitmapId& id) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(compositor_frame_sink_);
   compositor_frame_sink_->DidAllocateSharedBitmap(
-      std::move(buffer), SharedBitmapIdToGpuMailboxPtr(id));
+      std::move(region), SharedBitmapIdToGpuMailboxPtr(id));
 }
 
 void VideoFrameSubmitter::DidDeleteSharedBitmap(const viz::SharedBitmapId& id) {
diff --git a/third_party/blink/renderer/platform/graphics/video_frame_submitter.h b/third_party/blink/renderer/platform/graphics/video_frame_submitter.h
index 14825ba..54c6a06 100644
--- a/third_party/blink/renderer/platform/graphics/video_frame_submitter.h
+++ b/third_party/blink/renderer/platform/graphics/video_frame_submitter.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <utility>
 
+#include "base/memory/read_only_shared_memory_region.h"
 #include "base/memory/weak_ptr.h"
 #include "base/threading/thread_checker.h"
 #include "base/time/time.h"
@@ -75,7 +76,7 @@
       const WTF::Vector<viz::ReturnedResource>& resources) override;
 
   // viz::SharedBitmapReporter implementation.
-  void DidAllocateSharedBitmap(mojo::ScopedSharedBufferHandle,
+  void DidAllocateSharedBitmap(base::ReadOnlySharedMemoryRegion,
                                const viz::SharedBitmapId&) override;
   void DidDeleteSharedBitmap(const viz::SharedBitmapId&) override;
 
diff --git a/third_party/blink/renderer/platform/graphics/video_frame_submitter_test.cc b/third_party/blink/renderer/platform/graphics/video_frame_submitter_test.cc
index 04b29b6..737afe9 100644
--- a/third_party/blink/renderer/platform/graphics/video_frame_submitter_test.cc
+++ b/third_party/blink/renderer/platform/graphics/video_frame_submitter_test.cc
@@ -8,6 +8,7 @@
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/memory/ptr_util.h"
+#include "base/memory/read_only_shared_memory_region.h"
 #include "base/test/scoped_task_environment.h"
 #include "base/test/simple_test_tick_clock.h"
 #include "base/time/time.h"
@@ -96,11 +97,11 @@
   MOCK_METHOD1(DidNotProduceFrame, void(const viz::BeginFrameAck&));
 
   MOCK_METHOD2(DidAllocateSharedBitmap_,
-               void(mojo::ScopedSharedBufferHandle* buffer,
+               void(base::ReadOnlySharedMemoryRegion* region,
                     gpu::mojom::blink::MailboxPtr* id));
-  void DidAllocateSharedBitmap(mojo::ScopedSharedBufferHandle buffer,
+  void DidAllocateSharedBitmap(base::ReadOnlySharedMemoryRegion region,
                                gpu::mojom::blink::MailboxPtr id) override {
-    DidAllocateSharedBitmap_(&buffer, &id);
+    DidAllocateSharedBitmap_(&region, &id);
   }
 
   MOCK_METHOD1(DidDeleteSharedBitmap_, void(gpu::mojom::blink::MailboxPtr* id));
diff --git a/third_party/blink/renderer/platform/mojo/interface_invalidator_test.cc b/third_party/blink/renderer/platform/mojo/interface_invalidator_test.cc
index 74cd25b7..8bbc5cc 100644
--- a/third_party/blink/renderer/platform/mojo/interface_invalidator_test.cc
+++ b/third_party/blink/renderer/platform/mojo/interface_invalidator_test.cc
@@ -7,9 +7,9 @@
 
 #include "base/bind.h"
 #include "base/macros.h"
-#include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "base/test/bind_test_util.h"
+#include "base/test/scoped_task_environment.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/public/cpp/bindings/interface_ptr.h"
 #include "mojo/public/interfaces/bindings/tests/ping_service.mojom-blink.h"
@@ -97,7 +97,7 @@
   ~InterfaceInvalidatorTest() override {}
 
  private:
-  base::MessageLoop message_loop_;
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
 
   DISALLOW_COPY_AND_ASSIGN(InterfaceInvalidatorTest);
 };
diff --git a/third_party/blink/renderer/platform/mojo/kurl_security_origin_test.cc b/third_party/blink/renderer/platform/mojo/kurl_security_origin_test.cc
index db429023..f41ec14 100644
--- a/third_party/blink/renderer/platform/mojo/kurl_security_origin_test.cc
+++ b/third_party/blink/renderer/platform/mojo/kurl_security_origin_test.cc
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 #include "base/macros.h"
-#include "base/message_loop/message_loop.h"
+#include "base/test/scoped_task_environment.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/mojom/url_test.mojom-blink.h"
@@ -35,7 +35,7 @@
 
 // Mojo version of chrome IPC test in url/ipc/url_param_traits_unittest.cc.
 TEST(KURLSecurityOriginStructTraitsTest, Basic) {
-  base::MessageLoop message_loop;
+  base::test::ScopedTaskEnvironment scoped_task_environment;
 
   url::mojom::blink::UrlTestPtr proxy;
   UrlTestImpl impl(MakeRequest(&proxy));
diff --git a/third_party/blink/renderer/platform/timer_test.cc b/third_party/blink/renderer/platform/timer_test.cc
index 4ff74e0..aaa2bcb 100644
--- a/third_party/blink/renderer/platform/timer_test.cc
+++ b/third_party/blink/renderer/platform/timer_test.cc
@@ -6,8 +6,8 @@
 
 #include <memory>
 #include <queue>
-#include "base/message_loop/message_loop.h"
 #include "base/single_thread_task_runner.h"
+#include "base/test/scoped_task_environment.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/platform.h"
@@ -83,7 +83,7 @@
       platform_;
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
 
-  base::MessageLoop message_loop_;
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
 };
 
 class OnHeapTimerOwner final
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 23709bf..5d4730b 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -5300,12 +5300,6 @@
 
 crbug.com/919272 external/wpt/resource-timing/resource-timing.html [ Skip ]
 
-### virtual/streams-native/http/tests/streams/transferable/
-crbug.com/902633 virtual/streams-native/http/tests/streams/transferable/writable-stream.html [ Timeout ]
-crbug.com/902633 virtual/streams-native/http/tests/streams/transferable/shared-worker.html [ Pass Failure ]
-crbug.com/902633 virtual/streams-native/http/tests/streams/transferable/worker.html [ Pass Failure ]
-
-
 # Sheriff 2019-01-03
 crbug.com/918905 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/block-size-with-min-or-max-content-table-1b.html [ Pass Failure ]
 
@@ -5797,9 +5791,9 @@
 crbug.com/966345 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/class_object/class_white-space_normal_wrapped.html [ Failure ]
 crbug.com/966345 external/wpt/webvtt/rendering/cues-with-video/processing-model/selectors/cue_function/class_object/class_white-space_pre-line_wrapped.html [ Failure ]
 
+crbug.com/905971 virtual/blink-cors/external/wpt/service-workers/service-worker/detached-context.https.html [ Pass Crash ]
 crbug.com/905971 virtual/blink-cors/http/tests/security/img-redirect-to-crossorigin-credentials.html [ Failure ]
 crbug.com/905971 virtual/blink-cors/http/tests/security/script-crossorigin-redirect-credentials.html [ Failure ]
-crbug.com/905971 [ Win ] virtual/blink-cors/external/wpt/service-workers/service-worker/detached-context.https.html [ Pass Crash ]
 
 # Sheriff 2019-06-04
 crbug.com/970135 [ Mac ] virtual/focusless-spat-nav/fast/spatial-navigation/focusless/snav-focusless-interested-element-indicated.html [ Failure ]
diff --git a/third_party/blink/web_tests/external/wpt/referrer-policy/generic/referrer-policy-test-case.sub.js b/third_party/blink/web_tests/external/wpt/referrer-policy/generic/referrer-policy-test-case.sub.js
index b286760..0c0f381 100644
--- a/third_party/blink/web_tests/external/wpt/referrer-policy/generic/referrer-policy-test-case.sub.js
+++ b/third_party/blink/web_tests/external/wpt/referrer-policy/generic/referrer-policy-test-case.sub.js
@@ -125,14 +125,52 @@
       policyDeliveries: [delivery]
     };
 
+    let currentURL = location.toString();
     const expectedReferrer =
-      referrerUrlResolver[scenario.referrer_url](location.toString());
+      referrerUrlResolver[scenario.referrer_url](currentURL);
 
     // Request in the top-level document.
     promise_test(_ => {
+      return invokeRequest(subresource, [])
+        .then(result => checkResult(expectedReferrer, result));
+    }, testDescription);
+
+    // `Referer` headers with length over 4k are culled down to an origin, so, let's test around
+    // that boundary for tests that would otherwise return the complete URL.
+    if (scenario.referrer_url == "stripped-referrer") {
+      promise_test(_ => {
+        history.pushState(null, null, "/");
+        history.replaceState(null, null, "A".repeat(4096 - location.href.length - 1));
+        const expectedReferrer = location.href;
+        // Ensure that we don't load the same URL as the previous test.
+        subresource.url += "&-1";
         return invokeRequest(subresource, [])
-          .then(result => checkResult(expectedReferrer, result));
-      }, testDescription);
+          .then(result => checkResult(location.href, result))
+          .finally(_ => history.back());
+      }, "`Referer` header with length < 4k is not stripped to an origin.");
+
+      promise_test(_ => {
+        history.pushState(null, null, "/");
+        history.replaceState(null, null, "A".repeat(4096 - location.href.length));
+        const expectedReferrer = location.href;
+        // Ensure that we don't load the same URL as the previous test.
+        subresource.url += "&0";
+        return invokeRequest(subresource, [])
+          .then(result => checkResult(expectedReferrer, result))
+          .finally(_ => history.back());
+      }, "`Referer` header with length == 4k is not stripped to an origin.");
+
+      promise_test(_ => {
+        const originString = referrerUrlResolver["origin"](currentURL);
+        history.pushState(null, null, "/");
+        history.replaceState(null, null, "A".repeat(4096 - location.href.length + 1));
+        // Ensure that we don't load the same URL as the previous test.
+        subresource.url += "&+1";
+        return invokeRequest(subresource, [])
+          .then(result => checkResult(originString, result))
+          .finally(_ => history.back());
+      }, "`Referer` header with length > 4k is stripped to an origin.");
+    }
 
     // We test requests from inside iframes only for <img> tags.
     // This is just to preserve the previous test coverage.
diff --git a/third_party/blink/web_tests/virtual/streams-native/http/tests/streams/transferable/readable-stream-expected.txt b/third_party/blink/web_tests/virtual/streams-native/http/tests/streams/transferable/readable-stream-expected.txt
deleted file mode 100644
index a398705f..0000000
--- a/third_party/blink/web_tests/virtual/streams-native/http/tests/streams/transferable/readable-stream-expected.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-This is a testharness.js-based test.
-FAIL sending one chunk through a transferred stream should work promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL sending ten chunks through a transferred stream should work promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL sending ten chunks one at a time should work promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL sending ten chunks on demand should work promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL transferring a stream should relieve backpressure assert_array_equals: pull() should have been called lengths differ, expected 1 got 0
-FAIL transferring a stream should add one chunk to the queue size promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL the extra queue from transferring is counted in chunks promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL cancel should be propagated to the original promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL cancel should abort a pending read() promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL stream cancel should not wait for underlying source cancel promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL serialization should not happen until the value is read promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL transferring a non-serializable chunk should error both sides promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL errors should be passed through promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL race between cancel() and error() should leave sides in different states promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL race between cancel() and close() should be benign promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL race between cancel() and enqueue() should be benign promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/virtual/streams-native/http/tests/streams/transferable/reason-expected.txt b/third_party/blink/web_tests/virtual/streams-native/http/tests/streams/transferable/reason-expected.txt
deleted file mode 100644
index e381006..0000000
--- a/third_party/blink/web_tests/virtual/streams-native/http/tests/streams/transferable/reason-expected.txt
+++ /dev/null
@@ -1,25 +0,0 @@
-This is a testharness.js-based test.
-FAIL reason with a simple value of 'hi' should be preserved promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL reason with a simple value of '	\r
-' should be preserved promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL reason with a simple value of '7' should be preserved promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL reason with a simple value of '3' should be preserved promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL reason with a simple value of 'undefined' should be preserved promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL reason with a simple value of 'null' should be preserved promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL reason with a simple value of 'true' should be preserved promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL reason with a simple value of 'false' should be preserved promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL reason with a type of 'symbol' should be squished to undefined promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL reason with a type of 'function' should be squished to undefined promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL number with a value of 'NaN' should be squished to null promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL number with a value of 'Infinity' should be squished to null promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL objects that can be completely expressed in JSON should be preserved promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL objects that cannot be expressed in JSON should result in a TypeError promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL the type and message of a TypeError should be preserved promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL other attributes of a TypeError should not be preserved promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL a TypeError message should not be preserved if it is not a string promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL a TypeError message should not be preserved if it is a getter promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL a TypeError message should not be preserved if it is inherited promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL DOMException errors should be preserved promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-FAIL RangeErrors should not be preserved promise_test: Unhandled rejection with value: object "Error: what is this thing: "null"?"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/virtual/streams-native/http/tests/streams/transferable/service-worker-expected.txt b/third_party/blink/web_tests/virtual/streams-native/http/tests/streams/transferable/service-worker-expected.txt
deleted file mode 100644
index d487631..0000000
--- a/third_party/blink/web_tests/virtual/streams-native/http/tests/streams/transferable/service-worker-expected.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-This is a testharness.js-based test.
-PASS service-worker
-FAIL serviceWorker.controller.postMessage should be able to transfer a ReadableStream assert_true: the original stream should be locked expected true got false
-FAIL postMessage in a service worker should be able to transfer ReadableStream promise_test: Unhandled rejection with value: "BAD: TypeError: Cannot read property 'constructor' of null"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/virtual/streams-native/http/tests/streams/transferable/shared-worker-expected.txt b/third_party/blink/web_tests/virtual/streams-native/http/tests/streams/transferable/shared-worker-expected.txt
deleted file mode 100644
index a61ccf35..0000000
--- a/third_party/blink/web_tests/virtual/streams-native/http/tests/streams/transferable/shared-worker-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-FAIL worker.postMessage should be able to transfer a ReadableStream assert_true: the original stream should be locked expected true got false
-FAIL postMessage in a worker should be able to transfer a ReadableStream promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'constructor' of null"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/virtual/streams-native/http/tests/streams/transferable/transform-stream-expected.txt b/third_party/blink/web_tests/virtual/streams-native/http/tests/streams/transferable/transform-stream-expected.txt
deleted file mode 100644
index fddd996..0000000
--- a/third_party/blink/web_tests/virtual/streams-native/http/tests/streams/transferable/transform-stream-expected.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-This is a testharness.js-based test.
-FAIL window.postMessage should be able to transfer a TransformStream assert_true: the readable side should be locked expected true got false
-PASS a TransformStream with a locked writable should not be transferable
-PASS a TransformStream with a locked readable should not be transferable
-PASS a TransformStream with both sides locked should not be transferable
-FAIL piping through transferred transforms should work Cannot read property 'source' of null
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/virtual/streams-native/http/tests/streams/transferable/window-expected.txt b/third_party/blink/web_tests/virtual/streams-native/http/tests/streams/transferable/window-expected.txt
deleted file mode 100644
index 8e6fa50..0000000
--- a/third_party/blink/web_tests/virtual/streams-native/http/tests/streams/transferable/window-expected.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-CONSOLE ERROR: line 23: Uncaught TypeError: Cannot read property 'start' of null
-CONSOLE ERROR: line 17: Uncaught (in promise) TypeError: Cannot read property 'constructor' of null
-CONSOLE ERROR: line 41: Uncaught (in promise) TypeError: Cannot read property 'addEventListener' of null
-CONSOLE ERROR: line 45: Uncaught (in promise) Error: assert_array_equals: there should be no ports in the event lengths differ, expected 0 got 1
-CONSOLE ERROR: line 5: Uncaught TypeError: Failed to execute 'postMessage' on 'Window': Value at index 0 is an untransferable 'null' value.
-This is a testharness.js-based test.
-Harness Error. harness_status.status = 1 , harness_status.message = assert_array_equals: there should be no ports in the event lengths differ, expected 0 got 1
-FAIL window.postMessage should be able to transfer a ReadableStream assert_true: the original stream should be locked expected true got false
-FAIL port.postMessage should be able to transfer a ReadableStream assert_true: the original stream should be locked expected true got false
-FAIL the same ReadableStream posted multiple times should arrive together object null is not iterable (cannot read property Symbol(Symbol.iterator))
-FAIL transfer to and from an iframe should work assert_array_equals: there should be no ports in the event lengths differ, expected 0 got 1
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/virtual/streams-native/http/tests/streams/transferable/worker-expected.txt b/third_party/blink/web_tests/virtual/streams-native/http/tests/streams/transferable/worker-expected.txt
deleted file mode 100644
index d3ca9a3..0000000
--- a/third_party/blink/web_tests/virtual/streams-native/http/tests/streams/transferable/worker-expected.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-CONSOLE ERROR: line 2: Uncaught TypeError: Failed to execute 'postMessage' on 'DedicatedWorkerGlobalScope': Value at index 0 is an untransferable 'null' value.
-CONSOLE ERROR: line 2: Uncaught TypeError: Failed to execute 'postMessage' on 'DedicatedWorkerGlobalScope': Value at index 0 is an untransferable 'null' value.
-This is a testharness.js-based test.
-FAIL worker.postMessage should be able to transfer a ReadableStream assert_true: the original stream should be locked expected true got false
-FAIL postMessage in a worker should be able to transfer a ReadableStream promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'constructor' of null"
-FAIL terminating a worker should not error the stream promise_test: Unhandled rejection with value: "error in worker"
-Harness: the test ran to completion.
-
diff --git a/third_party/freetype/README.chromium b/third_party/freetype/README.chromium
index fd6906a..3171e26 100644
--- a/third_party/freetype/README.chromium
+++ b/third_party/freetype/README.chromium
@@ -1,7 +1,7 @@
 Name: FreeType
 URL: http://www.freetype.org/
-Version: VER-2-10-0-55-gc949ab075
-Revision: c949ab0757a2514cd3a37b3e1e8390fd662a025b
+Version: VER-2-10-0-56-g711b593e4
+Revision: 711b593e4b589fbd726a4962ad492fc4e416355d
 License: Custom license "inspired by the BSD, Artistic, and IJG (Independent
          JPEG Group) licenses"
 License File: src/docs/FTL.TXT
diff --git a/tools/ipc_fuzzer/message_tools/message_list.cc b/tools/ipc_fuzzer/message_tools/message_list.cc
index faf70177..df0f9ac 100644
--- a/tools/ipc_fuzzer/message_tools/message_list.cc
+++ b/tools/ipc_fuzzer/message_tools/message_list.cc
@@ -93,7 +93,7 @@
       result = false;
     }
     while (class_id > previous_class_id + 1) {
-      if (!base::ContainsValue(exemptions, previous_class_id + 1)) {
+      if (!base::Contains(exemptions, previous_class_id + 1)) {
         std::cout << "Missing message file for enum "
                   << class_id - (previous_class_id + 1)
                   <<  " before enum used by " << file_name << "\n";
@@ -108,7 +108,7 @@
   }
 
   while (LastIPCMsgStart > highest_class_id + 1) {
-    if (!base::ContainsValue(exemptions, highest_class_id + 1)) {
+    if (!base::Contains(exemptions, highest_class_id + 1)) {
       std::cout << "Missing message file for enum "
                 << LastIPCMsgStart - (highest_class_id + 1)
                 << " before enum LastIPCMsgStart\n";
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 92e762d..15bbfae 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -863,6 +863,7 @@
   <int value="1" label="PASSWORD"/>
   <int value="2" label="PAYMENT_INFO"/>
   <int value="3" label="ADDRESS_INFO"/>
+  <int value="4" label="TOUCH_TO_FILL_INFO"/>
 </enum>
 
 <enum name="AccessPasswordInSettingsEvent">
@@ -17066,7 +17067,6 @@
   <int value="559" label="KerberosAddAccountsAllowed"/>
   <int value="560" label="KerberosAccounts"/>
   <int value="561" label="StickyKeysEnabled"/>
-  <int value="562" label="DockedMagnifierEnabled"/>
   <int value="563" label="AppRecommendationZeroStateEnabled"/>
   <int value="564" label="BrowserSwitcherExternalGreylistUrl"/>
   <int value="565" label="PolicyDictionaryMultipleSourceMergeList"/>
@@ -45415,6 +45415,14 @@
   <int value="8" label="Metadata persistence failed"/>
 </enum>
 
+<enum name="PasswordUpdateLoginSyncError">
+  <int value="0" label="None"/>
+  <int value="1" label="DB Not Available"/>
+  <int value="2" label="No Records were Updated"/>
+  <int value="3" label="Encryption Service Failure"/>
+  <int value="4" label="DB Error"/>
+</enum>
+
 <enum name="PaymentRequestAbortReason">
   <int value="0" label="DismissedByUser"/>
   <int value="1" label="AbortedByMerchant"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 83e99ea..767e79da 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -2756,7 +2756,7 @@
 </histogram>
 
 <histogram name="Android.PhotoPicker.BitmapScalerTask" units="ms"
-    expires_after="M77">
+    expires_after="2020-06-01">
   <owner>finnur@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <summary>
@@ -2765,7 +2765,7 @@
 </histogram>
 
 <histogram name="Android.PhotoPicker.CacheHits" units="Hits"
-    expires_after="M77">
+    expires_after="2020-06-01">
   <owner>finnur@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <summary>
@@ -2775,7 +2775,7 @@
 </histogram>
 
 <histogram name="Android.PhotoPicker.DecodeRequests" units="Hits"
-    expires_after="M77">
+    expires_after="2020-06-01">
   <owner>finnur@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <summary>
@@ -2784,7 +2784,8 @@
   </summary>
 </histogram>
 
-<histogram name="Android.PhotoPicker.DecoderHostFailureOutOfMemory" units="%">
+<histogram name="Android.PhotoPicker.DecoderHostFailureOutOfMemory" units="%"
+    expires_after="2020-06-01">
   <owner>finnur@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <summary>
@@ -2798,7 +2799,7 @@
 </histogram>
 
 <histogram name="Android.PhotoPicker.DecoderHostFailureRuntime" units="%"
-    expires_after="M77">
+    expires_after="2020-06-01">
   <owner>finnur@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <summary>
@@ -2812,7 +2813,7 @@
 </histogram>
 
 <histogram name="Android.PhotoPicker.DialogAction"
-    enum="PhotoPickerDialogAction" expires_after="M77">
+    enum="PhotoPickerDialogAction" expires_after="2020-06-01">
   <owner>finnur@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <summary>
@@ -2821,7 +2822,7 @@
 </histogram>
 
 <histogram name="Android.PhotoPicker.EnumeratedFiles" units="Files"
-    expires_after="M77">
+    expires_after="2020-06-01">
   <owner>finnur@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <summary>
@@ -2831,7 +2832,7 @@
 </histogram>
 
 <histogram name="Android.PhotoPicker.EnumeratedRate"
-    units="Files per 10th of a second" expires_after="M77">
+    units="Files per 10th of a second" expires_after="2020-06-01">
   <owner>finnur@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <summary>
@@ -2841,7 +2842,7 @@
 </histogram>
 
 <histogram name="Android.PhotoPicker.EnumerationTime" units="ms"
-    expires_after="M77">
+    expires_after="2020-06-01">
   <owner>finnur@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <summary>
@@ -2850,7 +2851,8 @@
   </summary>
 </histogram>
 
-<histogram name="Android.PhotoPicker.ExifOrientation" enum="ExifOrientation">
+<histogram name="Android.PhotoPicker.ExifOrientation" enum="ExifOrientation"
+    expires_after="2020-06-01">
   <owner>finnur@chromium.org</owner>
   <summary>
     Records whether the EXIF orientation directive is present and what it is set
@@ -2859,7 +2861,8 @@
   </summary>
 </histogram>
 
-<histogram name="Android.PhotoPicker.ImageByteCount" units="KB">
+<histogram name="Android.PhotoPicker.ImageByteCount" units="KB"
+    expires_after="2020-06-01">
   <owner>finnur@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <summary>
@@ -2868,7 +2871,8 @@
   </summary>
 </histogram>
 
-<histogram name="Android.PhotoPicker.ImageDecodeTime" units="ms">
+<histogram name="Android.PhotoPicker.ImageDecodeTime" units="ms"
+    expires_after="2020-06-01">
   <owner>finnur@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <summary>
@@ -2878,7 +2882,7 @@
 </histogram>
 
 <histogram name="Android.PhotoPicker.RequestProcessTime" units="ms"
-    expires_after="M77">
+    expires_after="2020-06-01">
   <owner>finnur@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <summary>
@@ -2888,7 +2892,8 @@
   </summary>
 </histogram>
 
-<histogram name="Android.PhotoPicker.UpscaleLowResBitmap" units="ms">
+<histogram name="Android.PhotoPicker.UpscaleLowResBitmap" units="ms"
+    expires_after="2020-06-01">
   <owner>finnur@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <summary>
@@ -3010,7 +3015,7 @@
 </histogram>
 
 <histogram name="Android.SelectFileDialogImgCount" units="images"
-    expires_after="M77">
+    expires_after="2020-06-01">
   <owner>finnur@chromium.org</owner>
   <owner>peter@chromium.org</owner>
   <summary>
@@ -3022,7 +3027,7 @@
 </histogram>
 
 <histogram name="Android.SelectFileDialogScope" enum="SelectFileDialogScope"
-    expires_after="M77">
+    expires_after="2020-06-01">
   <owner>peter@chromium.org</owner>
   <summary>
     Records the scope of accepted content for a select file dialog when shown by
@@ -90366,6 +90371,17 @@
   </summary>
 </histogram>
 
+<histogram name="PasswordManager.ApplySyncChanges.UpdateLoginSyncError"
+    enum="PasswordUpdateLoginSyncError" expires_after="M80">
+  <owner>mamir@chromium.org</owner>
+  <owner>mastiz@chromium.org</owner>
+  <summary>
+    Records different results upon updating a remote password to the password
+    manager. It is recorded every time after receiving remote password
+    incremental updates from the server.
+  </summary>
+</histogram>
+
 <histogram name="PasswordManager.ApplySyncChangesState"
     enum="PasswordApplySyncChangesState" expires_after="M80">
   <owner>mamir@chromium.org</owner>
@@ -91192,6 +91208,17 @@
   </summary>
 </histogram>
 
+<histogram name="PasswordManager.MergeSyncData.UpdateLoginSyncError"
+    enum="PasswordUpdateLoginSyncError" expires_after="M80">
+  <owner>mamir@chromium.org</owner>
+  <owner>mastiz@chromium.org</owner>
+  <summary>
+    Records different results upon updating a remote password in the password
+    manager. It is recorded during the initial sync when merging remote and
+    local data.
+  </summary>
+</histogram>
+
 <histogram name="PasswordManager.MultiAccountPasswordUpdateAction"
     enum="MultiAccountUpdateBubbleUserAction" expires_after="2018-03-29">
   <obsolete>
@@ -129906,6 +129933,23 @@
   </summary>
 </histogram>
 
+<histogram name="Tab.Count.Guest" expires_after="M85">
+  <owner>rhalavati@chromium.org</owner>
+  <owner>chrome-privacy-core@google.com</owner>
+  <summary>
+    Number of open tabs in each guest window. Recorded when a new tab is opened.
+  </summary>
+</histogram>
+
+<histogram name="Tab.Count.Incognito" expires_after="M85">
+  <owner>rhalavati@chromium.org</owner>
+  <owner>chrome-privacy-core@google.com</owner>
+  <summary>
+    Number of open tabs in each incognito window. Recorded when a new tab is
+    opened.
+  </summary>
+</histogram>
+
 <histogram name="Tab.Deactivation.Bookmarked" enum="Boolean"
     expires_after="2016-11-11">
   <obsolete>
diff --git a/tools/perf/core/stacktrace_unittest.py b/tools/perf/core/stacktrace_unittest.py
index eff01f8..f739303 100644
--- a/tools/perf/core/stacktrace_unittest.py
+++ b/tools/perf/core/stacktrace_unittest.py
@@ -15,6 +15,7 @@
   # Stack traces do not currently work on 10.6, but they are also being
   # disabled shortly so just disable it for now.
   # All platforms except chromeos should at least have a valid minidump.
+  # Disabled on Android, flaky: crbug.com/971998.
   @decorators.Disabled('snowleopard', 'chromeos', 'android')
   def testValidDump(self):
     with self.assertRaises(exceptions.DevtoolsTargetCrashException) as c:
diff --git a/tools/perf/expectations.config b/tools/perf/expectations.config
index a3063b4..b52059d 100644
--- a/tools/perf/expectations.config
+++ b/tools/perf/expectations.config
@@ -339,50 +339,7 @@
 crbug.com/954949 [ Nexus5X_Webview ] system_health.memory_mobile/browse:news:washingtonpost [ Skip ]
 crbug.com/961417 [ Android_Go ] system_health.memory_mobile/browse:social:tumblr_infinite_scroll:2018 [ Skip ]
 
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/browse:media:flickr_infinite_scroll [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/browse:search:amp:2018 [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/load:games:lazors [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/load:games:spychase:2018 [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/load:media:flickr:2018 [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/load:news:irctc [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/load:search:baidu:2018 [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/load:search:google:2018 [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/load:tools:stackoverflow:2018 [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/background:search:google [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/browse:media:googleplaystore:2019 [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/browse:media:imgur [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/browse:media:facebook_photos [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/browse:media:youtube [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/browse:shopping:lazada [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/browse:tools:maps [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/load:media:dailymotion [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/load:news:qq [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/load:news:wikipedia:2018 [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/load:search:ebay:2018 [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/load:search:taobao [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/load:tools:dropbox [ Skip ]
-crbug.com/964960 [ Nexus_5X ] system_health.memory_mobile/background:social:facebook [ Skip ]
-crbug.com/964960 [ Nexus_5X ] system_health.memory_mobile/browse:social:facebook_infinite_scroll:2018 [ Skip ]
-crbug.com/964960 [ Nexus_5X ] system_health.memory_mobile/load:media:facebook_photos [ Skip ]
-crbug.com/964960 [ Nexus_5X ] system_health.memory_mobile/load:media:google_images:2018 [ Skip ]
-crbug.com/964960 [ Nexus_5X ] system_health.memory_mobile/load:media:soundcloud:2018 [ Skip ]
-crbug.com/964960 [ Nexus_5X ] system_health.memory_mobile/load:search:yahoo:2018 [ Skip ]
-crbug.com/964960 [ Nexus_5X ] system_health.memory_mobile/load:social:twitter [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/browse:news:cricbuzz [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/browse:news:reddit [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/browse:news:qq [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/browse:search:amp:sxg:2019 [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/browse:social:instagram [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/browse:social:twitter [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/browse:social:tumblr_infinite_scroll:2018 [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/load:chrome:blank [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/load:media:imgur:2018 [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/load:media:youtube:2018 [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/load:news:cnn:2018 [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/load:news:reddit [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/load:search:yandex:2018 [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/load:tools:docs [ Skip ]
-crbug.com/964804 [ Nexus_5X ] system_health.memory_mobile/load:tools:weather [ Skip ]
+crbug.com/964960 [ Nexus_5X ] system_health.memory_mobile/* [ Skip ]
 
 # Benchmark: tab_switching.typical_25
 crbug.com/747026 [ Mac ] tab_switching.typical_25/multitab:misc:typical24 [ Skip ]
diff --git a/tools/traffic_annotation/auditor/instance.cc b/tools/traffic_annotation/auditor/instance.cc
index 9ddac6d..4a33123 100644
--- a/tools/traffic_annotation/auditor/instance.cc
+++ b/tools/traffic_annotation/auditor/instance.cc
@@ -278,19 +278,19 @@
   std::set<int> fields;
   GetSemanticsFieldNumbers(&fields);
   for (const auto& item : kSemanticsFields) {
-    if (!base::ContainsKey(fields, item.first))
+    if (!base::Contains(fields, item.first))
       unspecifieds.push_back(item.second);
   }
 
   GetPolicyFieldNumbers(&fields);
   for (const auto& item : kPolicyFields) {
-    if (!base::ContainsKey(fields, item.first)) {
+    if (!base::Contains(fields, item.first)) {
       // If 'cookies_allowed = NO' is provided, ignore not having
       // 'cookies_allowed = YES'.
       if (item.first ==
               traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
                   kCookiesAllowedFieldNumber &&
-          base::ContainsKey(fields, -item.first))
+          base::Contains(fields, -item.first))
         continue;
 
       // If |cookies_store| is not provided, ignore if 'cookies_allowed = NO' is
@@ -298,7 +298,7 @@
       if (item.first ==
               traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
                   kCookiesStoreFieldNumber &&
-          base::ContainsKey(
+          base::Contains(
               fields,
               -traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
                   kCookiesAllowedFieldNumber))
@@ -306,9 +306,9 @@
 
       // If either of |chrome_policy| or |policy_exception_justification| are
       // avaliable, ignore not having the other one.
-      if (base::ContainsValue(kChromePolicyFields, item.first) &&
-          (base::ContainsKey(fields, kChromePolicyFields[0]) ||
-           base::ContainsKey(fields, kChromePolicyFields[1]))) {
+      if (base::Contains(kChromePolicyFields, item.first) &&
+          (base::Contains(fields, kChromePolicyFields[0]) ||
+           base::Contains(fields, kChromePolicyFields[1]))) {
         continue;
       }
       unspecifieds.push_back(item.second);
@@ -522,35 +522,35 @@
 
   // The values of the semantics and policy are set so that the tests would know
   // which fields were available before archive.
-  if (base::ContainsKey(
+  if (base::Contains(
           semantics_fields,
           traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics::
               kSenderFieldNumber)) {
     annotation.proto.mutable_semantics()->set_sender("[Archived]");
   }
 
-  if (base::ContainsKey(
+  if (base::Contains(
           semantics_fields,
           traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics::
               kDescriptionFieldNumber)) {
     annotation.proto.mutable_semantics()->set_description("[Archived]");
   }
 
-  if (base::ContainsKey(
+  if (base::Contains(
           semantics_fields,
           traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics::
               kTriggerFieldNumber)) {
     annotation.proto.mutable_semantics()->set_trigger("[Archived]");
   }
 
-  if (base::ContainsKey(
+  if (base::Contains(
           semantics_fields,
           traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics::
               kDataFieldNumber)) {
     annotation.proto.mutable_semantics()->set_data("[Archived]");
   }
 
-  if (base::ContainsKey(
+  if (base::Contains(
           semantics_fields,
           traffic_annotation::NetworkTrafficAnnotation_TrafficSemantics::
               kDestinationFieldNumber)) {
@@ -559,7 +559,7 @@
             NetworkTrafficAnnotation_TrafficSemantics_Destination_WEBSITE);
   }
 
-  if (base::ContainsKey(
+  if (base::Contains(
           policy_fields,
           traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
               kCookiesAllowedFieldNumber)) {
@@ -568,7 +568,7 @@
             NetworkTrafficAnnotation_TrafficPolicy_CookiesAllowed_YES);
   }
 
-  if (base::ContainsKey(
+  if (base::Contains(
           policy_fields,
           -traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
               kCookiesAllowedFieldNumber)) {
@@ -577,28 +577,28 @@
             NetworkTrafficAnnotation_TrafficPolicy_CookiesAllowed_NO);
   }
 
-  if (base::ContainsKey(
+  if (base::Contains(
           policy_fields,
           traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
               kCookiesStoreFieldNumber)) {
     annotation.proto.mutable_policy()->set_cookies_store("[Archived]");
   }
 
-  if (base::ContainsKey(
+  if (base::Contains(
           policy_fields,
           traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
               kSettingFieldNumber)) {
     annotation.proto.mutable_policy()->set_setting("[Archived]");
   }
 
-  if (base::ContainsKey(
+  if (base::Contains(
           policy_fields,
           traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
               kChromePolicyFieldNumber)) {
     annotation.proto.mutable_policy()->add_chrome_policy();
   }
 
-  if (base::ContainsKey(
+  if (base::Contains(
           policy_fields,
           traffic_annotation::NetworkTrafficAnnotation_TrafficPolicy::
               kPolicyExceptionJustificationFieldNumber)) {
diff --git a/tools/traffic_annotation/auditor/traffic_annotation_auditor.cc b/tools/traffic_annotation/auditor/traffic_annotation_auditor.cc
index eb2a867..281d832 100644
--- a/tools/traffic_annotation/auditor/traffic_annotation_auditor.cc
+++ b/tools/traffic_annotation/auditor/traffic_annotation_auditor.cc
@@ -89,7 +89,7 @@
 // TODO(https://crbug.com/690323): Update to a more general policy.
 bool PathFiltersMatch(const std::vector<std::string>& path_filters,
                       const std::string file_path) {
-  if (base::ContainsValue(path_filters, file_path))
+  if (base::Contains(path_filters, file_path))
     return true;
   for (const std::string& path_filter : path_filters) {
     if (path_filter.find(".") == std::string::npos &&
@@ -612,7 +612,7 @@
     return false;
 
   // Already checked?
-  if (base::ContainsKey(checked_dependencies_, call.file_path))
+  if (base::Contains(checked_dependencies_, call.file_path))
     return checked_dependencies_[call.file_path];
 
   std::string gn_output;
@@ -732,7 +732,7 @@
   }
 
   for (AnnotationInstance* instance : completing_annotations) {
-    if (!base::ContainsKey(used_completing_annotations, instance)) {
+    if (!base::Contains(used_completing_annotations, instance)) {
       errors_.push_back(
           AuditorResult(AuditorResult::Type::ERROR_INCOMPLETED_ANNOTATION,
                         instance->proto.unique_id()));
diff --git a/tools/traffic_annotation/auditor/traffic_annotation_auditor_ui.cc b/tools/traffic_annotation/auditor/traffic_annotation_auditor_ui.cc
index 6ccb370..5bcc9d0 100644
--- a/tools/traffic_annotation/auditor/traffic_annotation_auditor_ui.cc
+++ b/tools/traffic_annotation/auditor/traffic_annotation_auditor_ui.cc
@@ -432,7 +432,7 @@
   }
 
   for (const AuditorResult& result : raw_errors) {
-    if (base::ContainsKey(warning_types, result.type()))
+    if (base::Contains(warning_types, result.type()))
       warnings.push_back(result);
     else
       errors.push_back(result);
diff --git a/tools/traffic_annotation/auditor/traffic_annotation_auditor_unittest.cc b/tools/traffic_annotation/auditor/traffic_annotation_auditor_unittest.cc
index 5b0322ae..085cc5e6 100644
--- a/tools/traffic_annotation/auditor/traffic_annotation_auditor_unittest.cc
+++ b/tools/traffic_annotation/auditor/traffic_annotation_auditor_unittest.cc
@@ -203,11 +203,11 @@
 
   EXPECT_EQ(git_files.size(), base::size(kRelevantFiles));
   for (const char* filepath : kRelevantFiles) {
-    EXPECT_TRUE(base::ContainsValue(git_files, filepath));
+    EXPECT_TRUE(base::Contains(git_files, filepath));
   }
 
   for (const char* filepath : kIrrelevantFiles) {
-    EXPECT_FALSE(base::ContainsValue(git_files, filepath));
+    EXPECT_FALSE(base::Contains(git_files, filepath));
   }
 }
 
@@ -233,7 +233,7 @@
   file_paths.clear();
   filter.GetRelevantFiles(base::FilePath(), ignore_list, "", &file_paths);
   EXPECT_EQ(file_paths.size(), git_files_count - 1);
-  EXPECT_FALSE(base::ContainsValue(file_paths, ignore_list[0]));
+  EXPECT_FALSE(base::Contains(file_paths, ignore_list[0]));
 
   // Check if files are filtered based on given directory.
   ignore_list.clear();
@@ -410,7 +410,7 @@
       TrafficAnnotationAuditor::GetReservedIDsMap();
 
   for (int id : expected_ids) {
-    EXPECT_TRUE(base::ContainsKey(reserved_words, id));
+    EXPECT_TRUE(base::Contains(reserved_words, id));
     EXPECT_EQ(id, TrafficAnnotationAuditor::ComputeHashValue(
                       reserved_words.find(id)->second));
   }
diff --git a/tools/traffic_annotation/auditor/traffic_annotation_exporter.cc b/tools/traffic_annotation/auditor/traffic_annotation_exporter.cc
index ae0828a..d7f40968 100644
--- a/tools/traffic_annotation/auditor/traffic_annotation_exporter.cc
+++ b/tools/traffic_annotation/auditor/traffic_annotation_exporter.cc
@@ -185,7 +185,7 @@
     int content_hash_code = annotation.GetContentHashCode();
     // If annotation unique id is already in the imported annotations list,
     // check if other fields have changed.
-    if (base::ContainsKey(archive_, annotation.proto.unique_id())) {
+    if (base::Contains(archive_, annotation.proto.unique_id())) {
       ArchivedAnnotation* current = &archive_[annotation.proto.unique_id()];
 
       // Check second id.
@@ -197,7 +197,7 @@
       }
 
       // Check platform.
-      if (!base::ContainsValue(current->os_list, current_platform_)) {
+      if (!base::Contains(current->os_list, current_platform_)) {
         current->os_list.push_back(current_platform_);
         modified_ = true;
       }
@@ -236,10 +236,10 @@
 
   // If a none-reserved annotation is removed from current platform, update it.
   for (auto& item : archive_) {
-    if (base::ContainsValue(item.second.os_list, current_platform_) &&
+    if (base::Contains(item.second.os_list, current_platform_) &&
         item.second.content_hash_code != -1 &&
-        !base::ContainsKey(current_platform_hashcodes,
-                           item.second.unique_id_hash_code)) {
+        !base::Contains(current_platform_hashcodes,
+                        item.second.unique_id_hash_code)) {
       base::Erase(item.second.os_list, current_platform_);
       modified_ = true;
     }
@@ -247,7 +247,7 @@
 
   // If there is a new reserved id, add it.
   for (const auto& item : reserved_ids) {
-    if (!base::ContainsKey(archive_, item.second)) {
+    if (!base::Contains(archive_, item.second)) {
       ArchivedAnnotation new_item;
       new_item.unique_id_hash_code = item.first;
       new_item.os_list = all_supported_platforms_;
@@ -364,7 +364,7 @@
   // Check for annotation hash code duplications.
   std::map<int, std::string> used_codes;
   for (auto& item : archive_) {
-    if (base::ContainsKey(used_codes, item.second.unique_id_hash_code)) {
+    if (base::Contains(used_codes, item.second.unique_id_hash_code)) {
       AuditorResult error(AuditorResult::Type::ERROR_HASH_CODE_COLLISION);
       error.AddDetail(used_codes[item.second.unique_id_hash_code]);
       error.AddDetail(item.first);
@@ -387,7 +387,7 @@
   // Check that listed OSes are valid.
   for (const auto& pair : archive_) {
     for (const auto& os : pair.second.os_list) {
-      if (!base::ContainsValue(all_supported_platforms_, os)) {
+      if (!base::Contains(all_supported_platforms_, os)) {
         AuditorResult error(AuditorResult::Type::ERROR_INVALID_OS,
                             std::string(), kAnnotationsXmlPath.MaybeAsASCII(),
                             AuditorResult::kNoCodeLineSpecified);
@@ -463,7 +463,7 @@
   }
 
   for (const std::string& id : old_keys) {
-    if (base::ContainsKey(new_items, id) && old_items[id] != new_items[id]) {
+    if (base::Contains(new_items, id) && old_items[id] != new_items[id]) {
       message +=
           base::StringPrintf("\n\tUpdate line: '%s' --> '%s'",
                              old_items[id].c_str(), new_items[id].c_str());
diff --git a/tools/traffic_annotation/auditor/traffic_annotation_exporter.h b/tools/traffic_annotation/auditor/traffic_annotation_exporter.h
index 2d4e121..62786fe 100644
--- a/tools/traffic_annotation/auditor/traffic_annotation_exporter.h
+++ b/tools/traffic_annotation/auditor/traffic_annotation_exporter.h
@@ -71,7 +71,7 @@
 
   // Checks if the current platform is in the os list of archived annotation.
   bool MatchesCurrentPlatform(const ArchivedAnnotation& annotation) const {
-    return base::ContainsValue(annotation.os_list, current_platform_);
+    return base::Contains(annotation.os_list, current_platform_);
   }
 
   // Produces the list of annotations that are not defined in this platform.
diff --git a/tools/traffic_annotation/auditor/traffic_annotation_id_checker.cc b/tools/traffic_annotation/auditor/traffic_annotation_id_checker.cc
index 235dcdc..3d9c63a 100644
--- a/tools/traffic_annotation/auditor/traffic_annotation_id_checker.cc
+++ b/tools/traffic_annotation/auditor/traffic_annotation_id_checker.cc
@@ -55,7 +55,7 @@
     std::vector<AuditorResult>* errors) {
   for (AnnotationItem& item : annotations_) {
     for (int i = 0; i < item.ids_count; i++) {
-      if (base::ContainsKey(invalid_set, item.ids[i].hash_code)) {
+      if (base::Contains(invalid_set, item.ids[i].hash_code)) {
         errors->push_back(AuditorResult(error_type, item.ids[i].text,
                                         item.file_path, item.line_number));
       }
@@ -83,7 +83,7 @@
   std::map<int, std::string> collisions;
   for (AnnotationItem& item : annotations_) {
     for (int i = 0; i < item.ids_count; i++) {
-      if (!base::ContainsKey(collisions, item.ids[i].hash_code)) {
+      if (!base::Contains(collisions, item.ids[i].hash_code)) {
         // If item is loaded from archive, and it is the second id, do not keep
         // the id for checks. Archive just keeps the hash code of the second id
         // and the text value of it is not correct.
@@ -112,7 +112,7 @@
 
   // Check if first ids are unique.
   for (AnnotationItem& item : annotations_) {
-    if (!base::ContainsKey(first_ids, item.ids[0].hash_code)) {
+    if (!base::Contains(first_ids, item.ids[0].hash_code)) {
       first_ids[item.ids[0].hash_code] = &item;
     } else {
       errors->push_back(CreateRepeatedIDError(
@@ -124,7 +124,7 @@
   // type PARTIAL and owner of the first id should be of type COMPLETING.
   for (AnnotationItem& item : annotations_) {
     if (item.ids_count == 2 &&
-        base::ContainsKey(first_ids, item.ids[1].hash_code)) {
+        base::Contains(first_ids, item.ids[1].hash_code)) {
       if (item.type != AnnotationInstance::Type::ANNOTATION_PARTIAL ||
           first_ids[item.ids[1].hash_code]->type !=
               AnnotationInstance::Type::ANNOTATION_COMPLETING) {
@@ -139,7 +139,7 @@
   for (AnnotationItem& item : annotations_) {
     if (item.ids_count != 2)
       continue;
-    if (!base::ContainsKey(second_ids, item.ids[1].hash_code)) {
+    if (!base::Contains(second_ids, item.ids[1].hash_code)) {
       second_ids[item.ids[1].hash_code] = &item;
     } else {
       AnnotationInstance::Type other_type =
diff --git a/ui/accessibility/platform/ax_platform_node_auralinux.cc b/ui/accessibility/platform/ax_platform_node_auralinux.cc
index e3bde07..701ec8b 100644
--- a/ui/accessibility/platform/ax_platform_node_auralinux.cc
+++ b/ui/accessibility/platform/ax_platform_node_auralinux.cc
@@ -124,46 +124,21 @@
 // null if if the AtkObject is destroyed.
 AtkObject* g_active_top_level_frame = nullptr;
 
-// AtkTableCell was introduced in ATK 2.12. Ubuntu Trusty has ATK 2.10.
-// Compile-time checks are in place for ATK versions that are older than 2.12.
-// However, we also need runtime checks in case the version we are building
-// against is newer than the runtime version. To prevent a runtime error, we
-// check that we have a version of ATK that supports AtkTableCell. If we do,
-// we dynamically load the symbol; if we don't, the interface is absent from
-// the accessible object and its methods will not be exposed or callable.
-// The definitions below ensure we have no missing symbols. Note that in
-// environments where we have ATK > 2.12, the definitions of AtkTableCell and
-// AtkTableCellIface below are overridden by the runtime version.
-// TODO(accessibility) Remove these definitions, along with the use of
-// LoadTableCellMethods() when 2.12 is the minimum supported version.
-typedef struct _AtkTableCell AtkTableCell;
-typedef struct _AtkTableCellIface AtkTableCellIface;
-typedef GType (*cell_get_type_func)();
-typedef GPtrArray* (*get_column_header_cells_func)(AtkTableCell* cell);
-typedef GPtrArray* (*get_row_header_cells_func)(AtkTableCell* cell);
-typedef bool (*get_row_column_span_func)(AtkTableCell* cell,
-                                         gint* row,
-                                         gint* column,
-                                         gint* row_span,
-                                         gint* col_span);
+#if defined(ATK_216)
+constexpr AtkRole kStaticRole = ATK_ROLE_STATIC;
+constexpr AtkRole kSubscriptRole = ATK_ROLE_SUBSCRIPT;
+constexpr AtkRole kSuperscriptRole = ATK_ROLE_SUPERSCRIPT;
+#else
+constexpr AtkRole kStaticRole = ATK_ROLE_TEXT;
+constexpr AtkRole kSubscriptRole = ATK_ROLE_TEXT;
+constexpr AtkRole kSuperscriptRole = ATK_ROLE_TEXT;
+#endif
 
-cell_get_type_func cell_get_type = nullptr;
-get_column_header_cells_func get_column_header_cells = nullptr;
-get_row_header_cells_func get_row_header_cells = nullptr;
-get_row_column_span_func get_row_column_span = nullptr;
-
-bool LoadTableCellMethods() {
-  cell_get_type = reinterpret_cast<cell_get_type_func>(
-      dlsym(RTLD_DEFAULT, "atk_table_cell_get_type"));
-  get_column_header_cells = reinterpret_cast<get_column_header_cells_func>(
-      dlsym(RTLD_DEFAULT, "atk_table_cell_get_column_header_cells"));
-  get_row_header_cells = reinterpret_cast<get_row_header_cells_func>(
-      dlsym(RTLD_DEFAULT, "atk_table_cell_get_row_header_cells"));
-  get_row_column_span = reinterpret_cast<get_row_column_span_func>(
-      dlsym(RTLD_DEFAULT, "atk_table_cell_get_row_column_span"));
-
-  return cell_get_type;
-}
+#if defined(ATK_226)
+constexpr AtkRole kAtkFootnoteRole = ATK_ROLE_FOOTNOTE;
+#else
+constexpr AtkRole kAtkFootnoteRole = ATK_ROLE_LIST_ITEM;
+#endif
 
 AXPlatformNodeAuraLinux* AtkObjectToAXPlatformNodeAuraLinux(
     AtkObject* atk_object) {
@@ -310,19 +285,6 @@
   }
 }
 
-void IdsToGPtrArray(AXPlatformNodeDelegate* delegate,
-                    const std::vector<int32_t>& ids,
-                    GPtrArray* array) {
-  for (const auto& node_id : ids) {
-    if (AXPlatformNode* header = delegate->GetFromNodeID(node_id)) {
-      if (AtkObject* atk_header = header->GetNativeViewAccessible()) {
-        g_object_ref(atk_header);
-        g_ptr_array_add(array, atk_header);
-      }
-    }
-  }
-}
-
 const char* BuildDescriptionFromHeaders(AXPlatformNodeDelegate* delegate,
                                         const std::vector<int32_t>& ids) {
   std::vector<std::string> names;
@@ -364,149 +326,21 @@
   return g_slist_prepend(attribute_set, attribute);
 }
 
-// TODO(aleventhal) Remove this and use atk_role_get_name() once the following
-// GNOME bug is fixed: https://bugzilla.gnome.org/show_bug.cgi?id=795983
-const char* const kRoleNames[] = {
-    "invalid",  // ATK_ROLE_INVALID.
-    "accelerator label",
-    "alert",
-    "animation",
-    "arrow",
-    "calendar",
-    "canvas",
-    "check box",
-    "check menu item",
-    "color chooser",
-    "column header",
-    "combo box",
-    "dateeditor",
-    "desktop icon",
-    "desktop frame",
-    "dial",
-    "dialog",
-    "directory pane",
-    "drawing area",
-    "file chooser",
-    "filler",
-    "fontchooser",
-    "frame",
-    "glass pane",
-    "html container",
-    "icon",
-    "image",
-    "internal frame",
-    "label",
-    "layered pane",
-    "list",
-    "list item",
-    "menu",
-    "menu bar",
-    "menu item",
-    "option pane",
-    "page tab",
-    "page tab list",
-    "panel",
-    "password text",
-    "popup menu",
-    "progress bar",
-    "push button",
-    "radio button",
-    "radio menu item",
-    "root pane",
-    "row header",
-    "scroll bar",
-    "scroll pane",
-    "separator",
-    "slider",
-    "split pane",
-    "spin button",
-    "statusbar",
-    "table",
-    "table cell",
-    "table column header",
-    "table row header",
-    "tear off menu item",
-    "terminal",
-    "text",
-    "toggle button",
-    "tool bar",
-    "tool tip",
-    "tree",
-    "tree table",
-    "unknown",
-    "viewport",
-    "window",
-    "header",
-    "footer",
-    "paragraph",
-    "ruler",
-    "application",
-    "autocomplete",
-    "edit bar",
-    "embedded component",
-    "entry",
-    "chart",
-    "caption",
-    "document frame",
-    "heading",
-    "page",
-    "section",
-    "redundant object",
-    "form",
-    "link",
-    "input method window",
-    "table row",
-    "tree item",
-    "document spreadsheet",
-    "document presentation",
-    "document text",
-    "document web",
-    "document email",
-    "comment",
-    "list box",
-    "grouping",
-    "image map",
-    "notification",
-    "info bar",
-    "level bar",
-    "title bar",
-    "block quote",
-    "audio",
-    "video",
-    "definition",
-    "article",
-    "landmark",
-    "log",
-    "marquee",
-    "math",
-    "rating",
-    "timer",
-    "description list",
-    "description term",
-    "description value",
-    "static",
-    "math fraction",
-    "math root",
-    "subscript",
-    "superscript",
-    "footnote",  // ATK_ROLE_FOOTNOTE = 122.
-};
+AtkObject* GetActiveDescendantOfCurrentFocused() {
+  if (!g_current_focused)
+    return nullptr;
 
-#if defined(ATK_216)
-constexpr AtkRole kStaticRole = ATK_ROLE_STATIC;
-constexpr AtkRole kSubscriptRole = ATK_ROLE_SUBSCRIPT;
-constexpr AtkRole kSuperscriptRole = ATK_ROLE_SUPERSCRIPT;
-#else
-constexpr AtkRole kStaticRole = ATK_ROLE_TEXT;
-constexpr AtkRole kSubscriptRole = ATK_ROLE_TEXT;
-constexpr AtkRole kSuperscriptRole = ATK_ROLE_TEXT;
-#endif
+  auto* node = AtkObjectToAXPlatformNodeAuraLinux(g_current_focused);
+  if (!node)
+    return nullptr;
 
-#if defined(ATK_226)
-constexpr AtkRole kAtkFootnoteRole = ATK_ROLE_FOOTNOTE;
-#else
-constexpr AtkRole kAtkFootnoteRole = ATK_ROLE_LIST_ITEM;
-#endif
+  int32_t id =
+      node->GetIntAttribute(ax::mojom::IntAttribute::kActivedescendantId);
+  if (auto* descendant = node->GetDelegate()->GetFromNodeID(id))
+    return descendant->GetNativeViewAccessible();
+
+  return nullptr;
+}
 
 namespace atk_component {
 
@@ -1748,7 +1582,7 @@
 }
 
 GPtrArray* GetColumnHeaderCells(AtkTableCell* cell) {
-  GPtrArray* array = g_ptr_array_new();
+  GPtrArray* array = g_ptr_array_new_with_free_func(g_object_unref);
 
   // AtkTableCell is implemented on cells, row headers, and column headers.
   // Calling GetColHeaderNodeIds() on a column header cell will include that
@@ -1757,12 +1591,20 @@
   // headers for non-header cells.
   if (atk_object_get_role(ATK_OBJECT(cell)) != ATK_ROLE_TABLE_CELL)
     return array;
+  auto* obj = AtkObjectToAXPlatformNodeAuraLinux(ATK_OBJECT(cell));
+  if (!obj)
+    return array;
+  auto* table = obj->GetTable();
+  if (!table)
+    return array;
 
-  if (auto* obj = AtkObjectToAXPlatformNodeAuraLinux(ATK_OBJECT(cell))) {
-    if (auto* table = obj->GetTable()) {
-      std::vector<int32_t> ids =
-          table->GetDelegate()->GetColHeaderNodeIds(obj->GetTableColumn());
-      IdsToGPtrArray(table->GetDelegate(), ids, array);
+  std::vector<int32_t> ids =
+      table->GetDelegate()->GetColHeaderNodeIds(obj->GetTableColumn());
+  for (const auto& node_id : ids) {
+    if (AXPlatformNode* node = table->GetDelegate()->GetFromNodeID(node_id)) {
+      if (AtkObject* atk_node = node->GetNativeViewAccessible()) {
+        g_ptr_array_add(array, g_object_ref(atk_node));
+      }
     }
   }
 
@@ -1787,7 +1629,7 @@
 }
 
 GPtrArray* GetRowHeaderCells(AtkTableCell* cell) {
-  GPtrArray* array = g_ptr_array_new();
+  GPtrArray* array = g_ptr_array_new_with_free_func(g_object_unref);
 
   // AtkTableCell is implemented on cells, row headers, and column headers.
   // Calling GetRowHeaderNodeIds() on a row header cell will include that
@@ -1796,12 +1638,20 @@
   // headers for non-header cells.
   if (atk_object_get_role(ATK_OBJECT(cell)) != ATK_ROLE_TABLE_CELL)
     return array;
+  auto* obj = AtkObjectToAXPlatformNodeAuraLinux(ATK_OBJECT(cell));
+  if (!obj)
+    return array;
+  auto* table = obj->GetTable();
+  if (!table)
+    return array;
 
-  if (auto* obj = AtkObjectToAXPlatformNodeAuraLinux(ATK_OBJECT(cell))) {
-    if (auto* table = obj->GetTable()) {
-      std::vector<int32_t> ids =
-          table->GetDelegate()->GetRowHeaderNodeIds(obj->GetTableRow());
-      IdsToGPtrArray(table->GetDelegate(), ids, array);
+  std::vector<int32_t> ids =
+      table->GetDelegate()->GetRowHeaderNodeIds(obj->GetTableRow());
+  for (const auto& node_id : ids) {
+    if (AXPlatformNode* node = table->GetDelegate()->GetFromNodeID(node_id)) {
+      if (AtkObject* atk_node = node->GetNativeViewAccessible()) {
+        g_ptr_array_add(array, g_object_ref(atk_node));
+      }
     }
   }
 
@@ -2011,6 +1861,25 @@
 
 }  // namespace
 
+// static
+base::Optional<AtkTableCellInterface> AtkTableCellInterface::Get() {
+  static base::Optional<AtkTableCellInterface> interface = base::nullopt;
+  if (interface.has_value())
+    return interface->GetType ? interface : base::nullopt;
+
+  interface.emplace();
+  interface->GetType = reinterpret_cast<GetTypeFunc>(
+      dlsym(RTLD_DEFAULT, "atk_table_cell_get_type"));
+  interface->GetColumnHeaderCells = reinterpret_cast<GetColumnHeaderCellsFunc>(
+      dlsym(RTLD_DEFAULT, "atk_table_cell_get_column_header_cells"));
+  interface->GetRowHeaderCells = reinterpret_cast<GetRowHeaderCellsFunc>(
+      dlsym(RTLD_DEFAULT, "atk_table_cell_get_row_header_cells"));
+  interface->GetRowColumnSpan = reinterpret_cast<GetRowColumnSpanFunc>(
+      dlsym(RTLD_DEFAULT, "atk_table_cell_get_row_column_span"));
+  interface->initialized = true;
+  return interface->GetType ? interface : base::nullopt;
+}
+
 void AXPlatformNodeAuraLinux::EnsureGTypeInit() {
 #if !GLIB_CHECK_VERSION(2, 36, 0)
   static bool first_time = true;
@@ -2123,10 +1992,14 @@
   if (interface_mask_ & (1 << ATK_TABLE_INTERFACE))
     g_type_add_interface_static(type, ATK_TYPE_TABLE, &atk_table::Info);
 
-  // Run-time check to ensure AtkTableCell is supported (requires ATK 2.12).
-  if (LoadTableCellMethods() &&
-      interface_mask_ & (1 << ATK_TABLE_CELL_INTERFACE))
-    g_type_add_interface_static(type, cell_get_type(), &atk_table_cell::Info);
+  if (interface_mask_ & (1 << ATK_TABLE_CELL_INTERFACE)) {
+    // Run-time check to ensure AtkTableCell is supported (requires ATK 2.12).
+    auto interface = AtkTableCellInterface::Get();
+    if (interface.has_value()) {
+      g_type_add_interface_static(type, interface->GetType(),
+                                  &atk_table_cell::Info);
+    }
+  }
 
   return type;
 }
@@ -2570,22 +2443,6 @@
   }
 }
 
-static AtkObject* GetActiveDescendantOfCurrentFocused() {
-  if (!g_current_focused)
-    return nullptr;
-
-  auto* node = AtkObjectToAXPlatformNodeAuraLinux(g_current_focused);
-  if (!node)
-    return nullptr;
-
-  int32_t id =
-      node->GetIntAttribute(ax::mojom::IntAttribute::kActivedescendantId);
-  if (auto* descendant = node->GetDelegate()->GetFromNodeID(id))
-    return descendant->GetNativeViewAccessible();
-
-  return nullptr;
-}
-
 void AXPlatformNodeAuraLinux::GetAtkState(AtkStateSet* atk_state_set) {
   AXNodeData data = GetData();
 
@@ -2856,179 +2713,6 @@
   }
 }
 
-void AXPlatformNodeAuraLinux::AddAccessibilityTreeProperties(
-    base::DictionaryValue* dict) {
-  AtkRole role = GetAtkRole();
-  if (role != ATK_ROLE_UNKNOWN) {
-    int role_index = static_cast<int>(role);
-    dict->SetString("role", kRoleNames[role_index]);
-  }
-  const gchar* name = atk_object_get_name(atk_object_);
-  if (name)
-    dict->SetString("name", std::string(name));
-  const gchar* description = atk_object_get_description(atk_object_);
-  if (description)
-    dict->SetString("description", std::string(description));
-
-  AtkStateSet* state_set = atk_object_ref_state_set(atk_object_);
-  auto states = std::make_unique<base::ListValue>();
-  for (int i = ATK_STATE_INVALID; i < ATK_STATE_LAST_DEFINED; i++) {
-    AtkStateType state_type = static_cast<AtkStateType>(i);
-    if (atk_state_set_contains_state(state_set, state_type))
-      states->AppendString(atk_state_type_get_name(state_type));
-  }
-  dict->Set("states", std::move(states));
-
-  AtkRelationSet* relation_set = atk_object_ref_relation_set(atk_object_);
-  auto relations = std::make_unique<base::ListValue>();
-  for (int i = ATK_RELATION_NULL; i < ATK_RELATION_LAST_DEFINED; i++) {
-    AtkRelationType relation_type = static_cast<AtkRelationType>(i);
-    if (atk_relation_set_contains(relation_set, relation_type))
-      relations->AppendString(atk_relation_type_get_name(relation_type));
-  }
-  dict->Set("relations", std::move(relations));
-
-  AtkAttributeSet* attributes = atk_object_get_attributes(atk_object_);
-  for (AtkAttributeSet* attr = attributes; attr; attr = attr->next) {
-    AtkAttribute* attribute = static_cast<AtkAttribute*>(attr->data);
-    dict->SetString(attribute->name, attribute->value);
-  }
-  atk_attribute_set_free(attributes);
-
-  // Properties obtained via AtkValue.
-  auto value_properties = std::make_unique<base::ListValue>();
-  if (ATK_IS_VALUE(atk_object_)) {
-    AtkValue* value = ATK_VALUE(atk_object_);
-    GValue current = G_VALUE_INIT;
-    g_value_init(&current, G_TYPE_FLOAT);
-    atk_value_get_current_value(value, &current);
-    value_properties->AppendString(
-        base::StringPrintf("current=%f", g_value_get_float(&current)));
-
-    GValue minimum = G_VALUE_INIT;
-    g_value_init(&minimum, G_TYPE_FLOAT);
-    atk_value_get_minimum_value(value, &minimum);
-    value_properties->AppendString(
-        base::StringPrintf("minimum=%f", g_value_get_float(&minimum)));
-
-    GValue maximum = G_VALUE_INIT;
-    g_value_init(&maximum, G_TYPE_FLOAT);
-    atk_value_get_maximum_value(value, &maximum);
-    value_properties->AppendString(
-        base::StringPrintf("maximum=%f", g_value_get_float(&maximum)));
-  }
-  dict->Set("value", std::move(value_properties));
-
-  // Properties obtained via AtkTable.
-  auto table_properties = std::make_unique<base::ListValue>();
-  if (ATK_IS_TABLE(atk_object_)) {
-    AtkTable* table = ATK_TABLE(atk_object_);
-
-    // Column details.
-    int n_cols = atk_table_get_n_columns(table);
-    table_properties->AppendString(base::StringPrintf("cols=%i", n_cols));
-
-    std::vector<std::string> col_headers;
-    for (int i = 0; i < n_cols; i++) {
-      std::string header = atk_table_get_column_description(table, i);
-      if (!header.empty())
-        col_headers.push_back(base::StringPrintf("'%s'", header.c_str()));
-    }
-
-    if (!col_headers.size())
-      col_headers.push_back("NONE");
-
-    table_properties->AppendString(base::StringPrintf(
-        "headers=(%s);", base::JoinString(col_headers, ", ").c_str()));
-
-    // Row details.
-    int n_rows = atk_table_get_n_rows(table);
-    table_properties->AppendString(base::StringPrintf("rows=%i", n_rows));
-
-    std::vector<std::string> row_headers;
-    for (int i = 0; i < n_rows; i++) {
-      std::string header = atk_table_get_row_description(table, i);
-      if (!header.empty())
-        row_headers.push_back(base::StringPrintf("'%s'", header.c_str()));
-    }
-
-    if (!row_headers.size())
-      row_headers.push_back("NONE");
-
-    table_properties->AppendString(base::StringPrintf(
-        "headers=(%s);", base::JoinString(row_headers, ", ").c_str()));
-
-    // Caption details.
-    AtkObject* caption = atk_table_get_caption(table);
-    table_properties->AppendString(
-        base::StringPrintf("caption=%s;", caption ? "true" : "false"));
-
-    // Summarize information about the cells from the table's perspective here.
-    std::vector<std::string> span_info;
-    for (int r = 0; r < n_rows; r++) {
-      for (int c = 0; c < n_cols; c++) {
-        int row_span = atk_table_get_row_extent_at(table, r, c);
-        int col_span = atk_table_get_column_extent_at(table, r, c);
-        if (row_span != 1 || col_span != 1) {
-          span_info.push_back(base::StringPrintf("cell at %i,%i: %ix%i", r, c,
-                                                 row_span, col_span));
-        }
-      }
-    }
-    if (!span_info.size())
-      span_info.push_back("all: 1x1");
-
-    table_properties->AppendString(base::StringPrintf(
-        "spans=(%s)", base::JoinString(span_info, ", ").c_str()));
-  }
-
-  dict->Set("table", std::move(table_properties));
-
-  // Properties obtained via AtkTableCell, if possible. If we do not have at
-  // least ATK 2.12, use the same logic in our AtkTableCell implementation so
-  // that tests can still be run.
-  auto cell_properties = std::make_unique<base::ListValue>();
-  if (role == ATK_ROLE_TABLE_CELL || role == ATK_ROLE_COLUMN_HEADER ||
-      role == ATK_ROLE_ROW_HEADER) {
-    int row, col, row_span, col_span;
-    GPtrArray* col_headers;
-    GPtrArray* row_headers;
-    if (cell_get_type) {
-      AtkTableCell* cell = G_TYPE_CHECK_INSTANCE_CAST(
-          (atk_object_), cell_get_type(), AtkTableCell);
-      col_headers = get_column_header_cells(cell);
-      row_headers = get_row_header_cells(cell);
-      get_row_column_span(cell, &row, &col, &row_span, &col_span);
-    } else {
-      auto* obj = AtkObjectToAXPlatformNodeAuraLinux(atk_object_);
-      if (!obj)
-        return;
-      row = obj->GetTableRow();
-      col = obj->GetTableColumn();
-      row_span = obj->GetTableRowSpan();
-      col_span = obj->GetTableColumnSpan();
-      col_headers = g_ptr_array_new();
-      row_headers = g_ptr_array_new();
-      if (role == ATK_ROLE_TABLE_CELL) {
-        auto* delegate = obj->GetTable()->GetDelegate();
-        std::vector<int32_t> col_header_ids =
-            delegate->GetColHeaderNodeIds(col);
-        std::vector<int32_t> row_header_ids =
-            delegate->GetRowHeaderNodeIds(row);
-        IdsToGPtrArray(delegate, col_header_ids, col_headers);
-        IdsToGPtrArray(delegate, row_header_ids, row_headers);
-      }
-    }
-    cell_properties->AppendString(
-        base::StringPrintf("(row=%i, col=%i, row_span=%i, col_span=%i", row,
-                           col, row_span, col_span));
-    cell_properties->AppendString(
-        base::StringPrintf("n_row_headers=%i, n_col_headers=%i)",
-                           row_headers->len, col_headers->len));
-  }
-  dict->Set("cell", std::move(cell_properties));
-}
-
 gfx::NativeViewAccessible AXPlatformNodeAuraLinux::GetNativeViewAccessible() {
   return atk_object_;
 }
diff --git a/ui/accessibility/platform/ax_platform_node_auralinux.h b/ui/accessibility/platform/ax_platform_node_auralinux.h
index 8f24a35..243de66 100644
--- a/ui/accessibility/platform/ax_platform_node_auralinux.h
+++ b/ui/accessibility/platform/ax_platform_node_auralinux.h
@@ -28,6 +28,39 @@
 
 namespace ui {
 
+// AtkTableCell was introduced in ATK 2.12. Ubuntu Trusty has ATK 2.10.
+// Compile-time checks are in place for ATK versions that are older than 2.12.
+// However, we also need runtime checks in case the version we are building
+// against is newer than the runtime version. To prevent a runtime error, we
+// check that we have a version of ATK that supports AtkTableCell. If we do,
+// we dynamically load the symbol; if we don't, the interface is absent from
+// the accessible object and its methods will not be exposed or callable.
+// The definitions below ensure we have no missing symbols. Note that in
+// environments where we have ATK > 2.12, the definitions of AtkTableCell and
+// AtkTableCellIface below are overridden by the runtime version.
+// TODO(accessibility) Remove AtkTableCellInterface when 2.12 is the minimum
+// supported version.
+struct AX_EXPORT AtkTableCellInterface {
+  typedef struct _AtkTableCell AtkTableCell;
+  typedef struct _AtkTableCellIface AtkTableCellIface;
+  typedef GType (*GetTypeFunc)();
+  typedef GPtrArray* (*GetColumnHeaderCellsFunc)(AtkTableCell* cell);
+  typedef GPtrArray* (*GetRowHeaderCellsFunc)(AtkTableCell* cell);
+  typedef bool (*GetRowColumnSpanFunc)(AtkTableCell* cell,
+                                       gint* row,
+                                       gint* column,
+                                       gint* row_span,
+                                       gint* col_span);
+
+  GetTypeFunc GetType = nullptr;
+  GetColumnHeaderCellsFunc GetColumnHeaderCells = nullptr;
+  GetRowHeaderCellsFunc GetRowHeaderCells = nullptr;
+  GetRowColumnSpanFunc GetRowColumnSpan = nullptr;
+  bool initialized = false;
+
+  static base::Optional<AtkTableCellInterface> Get();
+};
+
 // Implements accessibility on Aura Linux using ATK.
 class AX_EXPORT AXPlatformNodeAuraLinux : public AXPlatformNodeBase {
  public:
@@ -46,7 +79,6 @@
 
   void DataChanged();
   void Destroy() override;
-  void AddAccessibilityTreeProperties(base::DictionaryValue* dict);
 
   AtkRole GetAtkRole();
   void GetAtkState(AtkStateSet* state_set);
diff --git a/ui/base/clipboard/clipboard_mac.mm b/ui/base/clipboard/clipboard_mac.mm
index 7ec5c99c..0a1ae03 100644
--- a/ui/base/clipboard/clipboard_mac.mm
+++ b/ui/base/clipboard/clipboard_mac.mm
@@ -339,6 +339,7 @@
     NOTREACHED() << "SkBitmapToNSImageWithColorSpace failed";
     return;
   }
+  // TODO (https://crbug.com/971916): Write NSImage directly to clipboard.
   // An API to ask the NSImage to write itself to the clipboard comes in 10.6 :(
   // For now, spit out the image as a TIFF.
   NSPasteboard* pb = GetPasteboard();
diff --git a/ui/base/ime/mojo/ime_struct_traits_unittest.cc b/ui/base/ime/mojo/ime_struct_traits_unittest.cc
index e261476..6ec9920 100644
--- a/ui/base/ime/mojo/ime_struct_traits_unittest.cc
+++ b/ui/base/ime/mojo/ime_struct_traits_unittest.cc
@@ -6,8 +6,8 @@
 
 #include <utility>
 
-#include "base/message_loop/message_loop.h"
 #include "base/stl_util.h"
+#include "base/test/scoped_task_environment.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/ime/mojo/ime_struct_traits_test.mojom.h"
@@ -35,7 +35,9 @@
     std::move(callback).Run(in);
   }
 
-  base::MessageLoop loop_;  // A MessageLoop is needed for Mojo IPC to work.
+  base::test::ScopedTaskEnvironment
+      scoped_task_environment_;  // A MessageLoop is needed for Mojo IPC to
+                                 // work.
   mojo::BindingSet<mojom::IMEStructTraitsTest> traits_test_bindings_;
 
   DISALLOW_COPY_AND_ASSIGN(IMEStructTraitsTest);
diff --git a/ui/file_manager/file_manager/foreground/js/metadata/metadata_parser.js b/ui/file_manager/file_manager/foreground/js/metadata/metadata_parser.js
index 6de0e42..dc4680d 100644
--- a/ui/file_manager/file_manager/foreground/js/metadata/metadata_parser.js
+++ b/ui/file_manager/file_manager/foreground/js/metadata/metadata_parser.js
@@ -3,89 +3,94 @@
 // found in the LICENSE file.
 
 /**
- * @param {MetadataParserLogger} parent Parent object.
- * @param {string} type Parser type.
- * @param {RegExp} urlFilter RegExp to match URLs.
- * @constructor
- * @struct
+ * @implements {MetadataParserLogger}
  */
-function MetadataParser(parent, type, urlFilter) {
-  this.parent_ = parent;
-  this.type = type;
-  this.urlFilter = urlFilter;
-  this.verbose = parent.verbose;
-  this.mimeType = 'unknown';
-}
+class MetadataParser {
+  /**
+   * @param {!MetadataParserLogger} parent Parent object.
+   * @param {string} type Parser type.
+   * @param {!RegExp} urlFilter RegExp to match URLs.
+   */
+  constructor(parent, type, urlFilter) {
+    /** @private @const {!MetadataParserLogger} */
+    this.parent_ = parent;
+    /** @public @const {string} */
+    this.type = type;
+    /** @public @const {!RegExp} */
+    this.urlFilter = urlFilter;
+    /** @public @const {boolean} */
+    this.verbose = parent.verbose;
+    /** @public {string} */
+    this.mimeType = 'unknown';
+  }
 
-/**
- * Output an error message.
- * @param {...(Object|string)} var_args Arguments.
- */
-MetadataParser.prototype.error = function(var_args) {
-  this.parent_.error.apply(this.parent_, arguments);
-};
+  /**
+   * Output an error message.
+   * @param {...(Object|string)} var_args Arguments.
+   */
+  error(var_args) {
+    this.parent_.error.apply(this.parent_, arguments);
+  }
 
-/**
- * Output a log message.
- * @param {...(Object|string)} var_args Arguments.
- */
-MetadataParser.prototype.log = function(var_args) {
-  this.parent_.log.apply(this.parent_, arguments);
-};
-
-/**
- * Output a log message if |verbose| flag is on.
- * @param {...(Object|string)} var_args Arguments.
- */
-MetadataParser.prototype.vlog = function(var_args) {
-  if (this.verbose) {
+  /**
+   * Output a log message.
+   * @param {...(Object|string)} var_args Arguments.
+   */
+  log(var_args) {
     this.parent_.log.apply(this.parent_, arguments);
   }
-};
 
-/**
- * @return {Object} Metadata object with the minimal set of properties.
- */
-MetadataParser.prototype.createDefaultMetadata = function() {
-  return {type: this.type, mimeType: this.mimeType};
-};
+  /**
+   * Output a log message if |verbose| flag is on.
+   * @param {...(Object|string)} var_args Arguments.
+   */
+  vlog(var_args) {
+    if (this.verbose) {
+      this.parent_.log.apply(this.parent_, arguments);
+    }
+  }
 
-/**
- * Utility function to read specified range of bytes from file
- * @param {File} file The file to read.
- * @param {number} begin Starting byte(included).
- * @param {number} end Last byte(excluded).
- * @param {function(File, ByteReader)} callback Callback to invoke.
- * @param {function(string)} onError Error handler.
- */
-MetadataParser.readFileBytes = (file, begin, end, callback, onError) => {
-  const fileReader = new FileReader();
-  fileReader.onerror = event => {
-    onError(event.type);
-  };
-  fileReader.onloadend = () => {
-    callback(
-        file,
-        new ByteReader(
-            /** @type {ArrayBuffer} */ (fileReader.result)));
-  };
-  fileReader.readAsArrayBuffer(file.slice(begin, end));
-};
+  /**
+   * @return {Object} Metadata object with the minimal set of properties.
+   */
+  createDefaultMetadata() {
+    return {type: this.type, mimeType: this.mimeType};
+  }
+
+  /**
+   * Utility function to read specified range of bytes from file
+   * @param {File} file The file to read.
+   * @param {number} begin Starting byte(included).
+   * @param {number} end Last byte(excluded).
+   * @param {function(File, ByteReader)} callback Callback to invoke.
+   * @param {function(string)} onError Error handler.
+   */
+  static readFileBytes(file, begin, end, callback, onError) {
+    const fileReader = new FileReader();
+    fileReader.onerror = event => {
+      onError(event.type);
+    };
+    fileReader.onloadend = () => {
+      callback(
+          file,
+          new ByteReader(
+              /** @type {ArrayBuffer} */ (fileReader.result)));
+    };
+    fileReader.readAsArrayBuffer(file.slice(begin, end));
+  }
+}
 
 /**
  * Base class for image metadata parsers.
- * @param {MetadataParserLogger} parent Parent object.
- * @param {string} type Image type.
- * @param {RegExp} urlFilter RegExp to match URLs.
- * @constructor
- * @struct
- * @extends {MetadataParser}
  */
-function ImageParser(parent, type, urlFilter) {
-  MetadataParser.apply(this, arguments);
-  this.mimeType = 'image/' + this.type;
+class ImageParser extends MetadataParser {
+  /**
+   * @param {!MetadataParserLogger} parent Parent object.
+   * @param {string} type Image type.
+   * @param {!RegExp} urlFilter RegExp to match URLs.
+   */
+  constructor(parent, type, urlFilter) {
+    super(parent, type, urlFilter);
+    this.mimeType = 'image/' + this.type;
+  }
 }
-
-ImageParser.prototype = {
-  __proto__: MetadataParser.prototype
-};
diff --git a/ui/gl/gl_gl_api_implementation.cc b/ui/gl/gl_gl_api_implementation.cc
index eb548c2f..68296bb 100644
--- a/ui/gl/gl_gl_api_implementation.cc
+++ b/ui/gl/gl_gl_api_implementation.cc
@@ -469,8 +469,7 @@
 }
 
 void RealGLApi::glUseProgramFn(GLuint program) {
-  ui::gl::ShaderTracking* shader_tracking =
-      ui::gl::ShaderTracking::GetInstance();
+  ShaderTracking* shader_tracking = ShaderTracking::GetInstance();
   if (shader_tracking) {
     std::vector<char> buffers[2];
     char* strings[2] = {nullptr, nullptr};
@@ -485,8 +484,8 @@
       GLuint shaders[2] = {0};
       glGetAttachedShadersFn(program, 2, &count, shaders);
       for (GLsizei ii = 0; ii < std::min(2, count); ++ii) {
-        buffers[ii].resize(ui::gl::ShaderTracking::kMaxShaderSize);
-        glGetShaderSourceFn(shaders[ii], ui::gl::ShaderTracking::kMaxShaderSize,
+        buffers[ii].resize(ShaderTracking::kMaxShaderSize);
+        glGetShaderSourceFn(shaders[ii], ShaderTracking::kMaxShaderSize,
                             nullptr, buffers[ii].data());
         strings[ii] = buffers[ii].data();
       }
diff --git a/ui/gl/shader_tracking.cc b/ui/gl/shader_tracking.cc
index eb4480c..8836224 100644
--- a/ui/gl/shader_tracking.cc
+++ b/ui/gl/shader_tracking.cc
@@ -7,7 +7,6 @@
 #include "base/logging.h"
 #include "ui/gl/gl_switches.h"
 
-namespace ui {
 namespace gl {
 
 // static
@@ -39,4 +38,3 @@
 }
 
 }  // namespace gl
-}  // namespace ui
diff --git a/ui/gl/shader_tracking.h b/ui/gl/shader_tracking.h
index b83ac6f..7fb7b58a 100644
--- a/ui/gl/shader_tracking.h
+++ b/ui/gl/shader_tracking.h
@@ -13,7 +13,6 @@
 #include "build/build_config.h"
 #include "ui/gl/gl_export.h"
 
-namespace ui {
 namespace gl {
 
 class GL_EXPORT ShaderTracking {
@@ -39,6 +38,5 @@
 };
 
 }  // namespace gl
-}  // namespace ui
 
 #endif  // UI_GL_SHADER_TRACKING_H_
diff --git a/ui/latency/mojo/struct_traits_unittest.cc b/ui/latency/mojo/struct_traits_unittest.cc
index 4f03eecc..6ada7af8 100644
--- a/ui/latency/mojo/struct_traits_unittest.cc
+++ b/ui/latency/mojo/struct_traits_unittest.cc
@@ -4,7 +4,7 @@
 
 #include <utility>
 
-#include "base/message_loop/message_loop.h"
+#include "base/test/scoped_task_environment.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/latency/mojo/latency_info_struct_traits.h"
@@ -32,7 +32,7 @@
     std::move(callback).Run(info);
   }
 
-  base::MessageLoop loop_;
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
   mojo::BindingSet<TraitsTestService> traits_test_bindings_;
   DISALLOW_COPY_AND_ASSIGN(StructTraitsTest);
 };
diff --git a/ui/login/OWNERS b/ui/login/OWNERS
index 9570de68..43c2ba7 100644
--- a/ui/login/OWNERS
+++ b/ui/login/OWNERS
@@ -1,4 +1,9 @@
+# (in PST)
 achuith@chromium.org
 alemate@chromium.org
 tbarzic@chromium.org
 xiyuan@chromium.org
+
+# (in CET)
+antrim@chromium.org
+rsorokin@chromium.org
diff --git a/ui/login/display_manager.js b/ui/login/display_manager.js
index f2cf183..f766edf 100644
--- a/ui/login/display_manager.js
+++ b/ui/login/display_manager.js
@@ -522,6 +522,9 @@
       if (oldStep.onBeforeHide)
         oldStep.onBeforeHide();
 
+      if (oldStep.defaultControl && oldStep.defaultControl.onBeforeHide)
+        oldStep.defaultControl.onBeforeHide();
+
       $('oobe').className = nextStepId;
 
       // Need to do this before calling newStep.onBeforeShow() so that new step
diff --git a/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.cc b/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.cc
index b6c85ef..3f163a0 100644
--- a/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.cc
+++ b/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.cc
@@ -558,7 +558,7 @@
 void WaylandBufferManagerHost::CreateShmBasedBuffer(
     gfx::AcceleratedWidget widget,
     mojo::ScopedHandle shm_fd,
-    size_t length,
+    uint64_t length,
     const gfx::Size& size,
     uint32_t buffer_id) {
   DCHECK(base::MessageLoopCurrentForUI::IsSet());
diff --git a/ui/views/controls/editable_combobox/editable_combobox.cc b/ui/views/controls/editable_combobox/editable_combobox.cc
index 410cb92..23b6e4f 100644
--- a/ui/views/controls/editable_combobox/editable_combobox.cc
+++ b/ui/views/controls/editable_combobox/editable_combobox.cc
@@ -249,6 +249,51 @@
   DISALLOW_COPY_AND_ASSIGN(EditableComboboxMenuModel);
 };
 
+// This class adds itself as the pre-target handler for the RootView of the
+// EditableCombobox. We use it to close the menu when press events happen in the
+// RootView but not inside the EditableComboobox's textfield.
+class EditableCombobox::EditableComboboxPreTargetHandler
+    : public ui::EventHandler {
+ public:
+  EditableComboboxPreTargetHandler(EditableCombobox* owner, View* root_view)
+      : owner_(owner), root_view_(root_view) {
+    root_view_->AddPreTargetHandler(this);
+  }
+
+  ~EditableComboboxPreTargetHandler() override { StopObserving(); }
+
+  // ui::EventHandler overrides.
+  void OnMouseEvent(ui::MouseEvent* event) override {
+    if (event->type() == ui::ET_MOUSE_PRESSED &&
+        event->flags() == event->changed_button_flags())
+      HandlePressEvent(event->root_location());
+  }
+  void OnTouchEvent(ui::TouchEvent* event) override {
+    if (event->type() == ui::ET_TOUCH_PRESSED)
+      HandlePressEvent(event->root_location());
+  }
+
+ private:
+  void HandlePressEvent(const gfx::Point& root_location) {
+    View* handler = root_view_->GetEventHandlerForPoint(root_location);
+    if (handler == owner_->textfield_ || handler == owner_->arrow_)
+      return;
+    owner_->CloseMenu();
+  }
+
+  void StopObserving() {
+    if (!root_view_)
+      return;
+    root_view_->RemovePreTargetHandler(this);
+    root_view_ = nullptr;
+  }
+
+  EditableCombobox* owner_;
+  View* root_view_;
+
+  DISALLOW_COPY_AND_ASSIGN(EditableComboboxPreTargetHandler);
+};
+
 ////////////////////////////////////////////////////////////////////////////////
 // EditableCombobox, public, non-overridden methods:
 EditableCombobox::EditableCombobox(
@@ -423,6 +468,7 @@
 
 void EditableCombobox::CloseMenu() {
   menu_runner_.reset();
+  pre_target_handler_.reset();
 }
 
 void EditableCombobox::OnItemSelected(int index) {
@@ -466,6 +512,15 @@
   if (!textfield_->HasFocus() || (menu_runner_ && menu_runner_->IsRunning()))
     return;
 
+  // Since we don't capture the mouse, we want to see the events that happen in
+  // the EditableCombobox's RootView to get a chance to close the menu if they
+  // happen outside |textfield_|. Events that happen over the menu belong to
+  // another Widget and they don't go through this pre-target handler.
+  // Events that happen outside both the menu and the RootView cause
+  // OnViewBlurred to be called, which also closes the menu.
+  pre_target_handler_ = std::make_unique<EditableComboboxPreTargetHandler>(
+      this, GetWidget()->GetRootView());
+
   gfx::Rect local_bounds = textfield_->GetLocalBounds();
   gfx::Point menu_position(local_bounds.origin());
   // Inset the menu's requested position so the border of the menu lines up
diff --git a/ui/views/controls/editable_combobox/editable_combobox.h b/ui/views/controls/editable_combobox/editable_combobox.h
index c0f59314..0386dfb 100644
--- a/ui/views/controls/editable_combobox/editable_combobox.h
+++ b/ui/views/controls/editable_combobox/editable_combobox.h
@@ -31,6 +31,7 @@
 namespace views {
 class EditableComboboxMenuModel;
 class EditableComboboxListener;
+class EditableComboboxPreTargetHandler;
 class MenuRunner;
 class Textfield;
 
@@ -104,6 +105,7 @@
 
  private:
   class EditableComboboxMenuModel;
+  class EditableComboboxPreTargetHandler;
 
   void CloseMenu();
 
@@ -142,6 +144,11 @@
   // The EditableComboboxMenuModel used by |menu_runner_|.
   std::unique_ptr<EditableComboboxMenuModel> menu_model_;
 
+  // Pre-target handler that closes the menu when press events happen in the
+  // root view (outside of the open menu's boundaries) but not inside the
+  // textfield.
+  std::unique_ptr<EditableComboboxPreTargetHandler> pre_target_handler_;
+
   // Typography context for the text written in the textfield and the options
   // shown in the drop-down menu.
   const int text_context_;
diff --git a/ui/views/controls/editable_combobox/editable_combobox_unittest.cc b/ui/views/controls/editable_combobox/editable_combobox_unittest.cc
index c9f42e5..396f86bb 100644
--- a/ui/views/controls/editable_combobox/editable_combobox_unittest.cc
+++ b/ui/views/controls/editable_combobox/editable_combobox_unittest.cc
@@ -100,6 +100,10 @@
   EditableCombobox* combobox_ = nullptr;
   View* dummy_focusable_view_ = nullptr;
 
+  // We make |combobox_| a child of another View to test different removal
+  // scenarios.
+  View* parent_of_combobox_ = nullptr;
+
   // Listener for our EditableCombobox.
   std::unique_ptr<DummyListener> listener_;
 
@@ -135,15 +139,17 @@
     const bool filter_on_edit,
     const bool show_on_empty,
     const EditableCombobox::Type type) {
+  parent_of_combobox_ = new View();
+  parent_of_combobox_->SetID(1);
   combobox_ =
       new EditableCombobox(std::make_unique<ui::SimpleComboboxModel>(items),
                            filter_on_edit, show_on_empty, type);
   listener_ = std::make_unique<DummyListener>();
   combobox_->set_listener(listener_.get());
-  combobox_->SetID(1);
+  combobox_->SetID(2);
   dummy_focusable_view_ = new View();
   dummy_focusable_view_->SetFocusBehavior(View::FocusBehavior::ALWAYS);
-  dummy_focusable_view_->SetID(2);
+  dummy_focusable_view_->SetID(3);
 
   InitWidget();
 }
@@ -154,11 +160,14 @@
   Widget::InitParams params =
       CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
   params.bounds = gfx::Rect(0, 0, 1000, 1000);
+  parent_of_combobox_->SetBoundsRect(gfx::Rect(0, 0, 500, 40));
+  combobox_->SetBoundsRect(gfx::Rect(0, 0, 500, 40));
+
   widget_->Init(params);
   View* container = new View();
   widget_->SetContentsView(container);
-  container->AddChildView(combobox_);
-  combobox_->SetBoundsRect(gfx::Rect(0, 0, 500, 40));
+  container->AddChildView(parent_of_combobox_);
+  parent_of_combobox_->AddChildView(combobox_);
   container->AddChildView(dummy_focusable_view_);
   widget_->Show();
 
@@ -256,6 +265,51 @@
   EXPECT_FALSE(IsMenuOpen());
 }
 
+TEST_F(EditableComboboxTest,
+       ClickOutsideEditableComboboxWithoutLosingFocusClosesMenu) {
+  InitEditableCombobox();
+  combobox_->GetTextfieldForTest()->RequestFocus();
+  EXPECT_TRUE(IsMenuOpen());
+
+  const gfx::Point outside_point(combobox_->x() + combobox_->width() + 1,
+                                 combobox_->y() + 1);
+  PerformClick(widget_, outside_point);
+
+  WaitForMenuClosureAnimation();
+  EXPECT_FALSE(IsMenuOpen());
+  EXPECT_TRUE(combobox_->GetTextfieldForTest()->HasFocus());
+}
+
+TEST_F(EditableComboboxTest, ClickTextfieldDoesntCloseMenu) {
+  InitEditableCombobox();
+  combobox_->GetTextfieldForTest()->RequestFocus();
+  EXPECT_TRUE(IsMenuOpen());
+
+  MenuRunner* menu_runner1 = combobox_->GetMenuRunnerForTest();
+  ClickTextfield();
+  MenuRunner* menu_runner2 = combobox_->GetMenuRunnerForTest();
+  EXPECT_TRUE(IsMenuOpen());
+
+  // Making sure the menu didn't close and reopen (causing a flicker).
+  EXPECT_EQ(menu_runner1, menu_runner2);
+}
+
+TEST_F(EditableComboboxTest, RemovingControlWhileMenuOpenClosesMenu) {
+  InitEditableCombobox();
+  combobox_->GetTextfieldForTest()->RequestFocus();
+  EXPECT_TRUE(IsMenuOpen());
+  parent_of_combobox_->RemoveChildView(combobox_);
+  EXPECT_EQ(nullptr, combobox_->GetMenuRunnerForTest());
+}
+
+TEST_F(EditableComboboxTest, RemovingParentOfControlWhileMenuOpenClosesMenu) {
+  InitEditableCombobox();
+  combobox_->GetTextfieldForTest()->RequestFocus();
+  EXPECT_TRUE(IsMenuOpen());
+  widget_->GetContentsView()->RemoveChildView(parent_of_combobox_);
+  EXPECT_EQ(nullptr, combobox_->GetMenuRunnerForTest());
+}
+
 TEST_F(EditableComboboxTest, LeftOrRightKeysMoveInTextfield) {
   InitEditableCombobox();
   combobox_->GetTextfieldForTest()->RequestFocus();